passport-magic-login

repository·master·Indexed 20 days ago

https://github.com/mxstbr/passport-magic-login

A Passport.js strategy for passwordless authentication using magic links. It enables users to sign up or log in via email or SMS by handling token generation, expiration, and verification. The library provides the MagicLoginStrategy for backend configuration, including secret management, callback URLs, and custom delivery functions via sendMagicLink.

Tokens
3K
Snippets
9
Records
9
Agent score
72%

What's inside passport-magic-login

  1. Set up Express routes for magic login

    master

    After configuring the strategy, you must register two routes in your Express application:

    1. The Send Route: A POST route where the client sends the user's identifier (email or phone). Use magicLogin.send as the handler.
    2. The Callback Route: A GET route matching your callbackUrl that uses passport.authenticate("magiclogin") to complete the authentication process.
    // This is where you POST to from the frontend
    app.post("/auth/magiclogin", magicLogin.send);
    
    // The standard passport callback setup
    app.get(magicLogin.callbackUrl, passport.authenticate("magiclogin"));
  2. Request a magic link from the frontend

    master

    To trigger the magic link process, POST a JSON request to your server's magic login endpoint. The payload must include a destination field (the user's email or phone number). You can include additional fields in the payload, which will be available in the backend verify method's payload argument.

    Upon success, the server returns a JSON response containing success: true and a code. It is recommended to display json.code to the user so they can verify they are clicking the link for the correct login attempt.

    // POST a request with the users email or phone number to the server
    fetch(`/auth/magiclogin`, {
      method: `POST`,
      body: JSON.stringify({
        // `destination` is required.
        destination: email,
        // However, you can POST anything in your payload and it will show up in your verify() method
        name: name,
      }),
      headers: { 'Content-Type': 'application/json' }
    })
      .then(res => res.json())
      .then(json => {
        if (json.success) {
          // The request successfully completed and the email to the user with the
          // magic login link was sent!
          // You can now prompt the user to click on the link in their email
          // We recommend you display json.code in the UI (!) so the user can verify
          // that they're clicking on the link for their _current_ login attempt
          document.body.innerText = json.code
        }
      })
  3. Configure the MagicLoginStrategy

    master

    To set up the backend, instantiate MagicLoginStrategy with the required configuration options. All options are mandatory.

    Required Options

    • secret: A long, unique, and secret string used to encrypt the authentication token.
    • callbackUrl: The URL where the user is redirected after clicking the magic link.
    • sendMagicLink: An async function called with the destination (e.g., email or phone) and the href (the path containing the token). Use this to deliver the link via email, SMS, etc.
    • verify: A function called once the user verifies their login attempt. It receives a payload (containing the destination and any other data POSTed from the client) and a callback. You must use this to find or create a user in your database and pass the user object to the callback.

    Optional Options

    • jwtOptions: An object containing options passed to the jwt.sign call (compatible with node-jsonwebtoken).
    import MagicLoginStrategy from "passport-magic-login"
    
    const magicLogin = new MagicLoginStrategy({
      secret: process.env.MAGIC_LINK_SECRET,
      callbackUrl: "/auth/magiclogin/callback",
      sendMagicLink: async (destination, href) => {
        await sendEmail({
          to: destination,
          body: `Click this link to finish logging in: https://yourcompany.com${href}`
        })
      },
      verify: (payload, callback) => {
        getOrCreateUserWithEmail(payload.destination)
          .then(user => {
            callback(null, user)
          })
          .catch(err => {
            callback(err)
          })
      },
      jwtOptions: {
        expiresIn: "2 days",
      }
    })
    
    passport.use(magicLogin)
  4. Configure MagicLoginStrategy Options

    master

    The MagicLoginStrategy requires an Options object to define how tokens are signed, where users are redirected, and how the magic link is delivered.

    Key configuration properties:

    • secret: The secret key used for signing and verifying JWTs.
    • callbackUrl: The URL where the user is redirected after clicking the magic link (e.g., https://myapp.com/verify).
    • jwtOptions: Optional SignOptions from jsonwebtoken to customize the JWT.
    • sendMagicLink: An asynchronous function responsible for delivering the link. It receives the destination (e.g., email), the generated href, a verificationCode, and the req object.
    • verify: A callback function used to validate the decoded token payload and identify the user.
    import MagicLoginStrategy, { Options } from 'passport-magic-login';
    
    const options: Options = {
      secret: 'your-very-secure-secret',
      callbackUrl: 'https://example.com/auth/callback',
      jwtOptions: { expiresIn: '1h' },
      sendMagicLink: async (destination, href, verificationCode, req) => {
        // Implement your own logic to send email/SMS
        await myEmailProvider.send({
          to: destination,
          body: `Click here to login: ${href}`
        });
      },
      verify: (payload, verifyCallback, req) => {
        // Logic to find or create a user based on payload
        const user = findUserByEmail(payload.destination);
        if (user) {
          verifyCallback(null, user);
        } else {
          verifyCallback(new Error('User not found'));
        }
      }
    };
    
    const strategy = new MagicLoginStrategy(options);
  5. Generate a magic link token with generateToken()

    master

    Use generateToken to create a signed JWT for magic links. It requires a secret string and a payload object of type JwtPayload. The payload must include a destination (e.g., the user's email) and a code (the magic token). You can optionally provide SignOptions to control token expiration; if omitted, the token defaults to an expiration of 60min.

    import { generateToken } from './token';
    
    const secret = 'your-very-secure-secret';
    const payload = {
      destination: 'user@example.com',
      code: 'magic-code-123',
      userId: 'user_01H2X'
    };
    
    const token = generateToken(secret, payload, { expiresIn: '1h' });
  6. Decode and verify a magic link token with decodeToken()

    master

    Use decodeToken to verify the authenticity of a magic link token and retrieve its payload. It requires the same secret used to sign the token. If the token is invalid, expired, or not a string, it will throw an error.

    import { decodeToken } from './token';
    
    const secret = 'your-very-secure-secret';
    const token = 'received-token-from-url';
    
    try {
      const decoded = decodeToken(secret, token) as any;
      console.log('Token valid. Destination:', decoded.destination);
    } catch (err) {
      console.error('Invalid or expired token:', err.message);
    }
  7. Use MagicLoginStrategy.authenticate to verify magic links

    master

    The .authenticate(req) method is used within a Passport strategy implementation to validate the magic link token provided in the request. It looks for the token in req.query.token or req.body.token.

    Workflow:

    1. Decodes the JWT using the configured secret.
    2. If decoding fails, it calls fail() with an error message.
    3. If decoding succeeds, it executes the verify callback provided in the Options.
    4. Based on the verify callback's result, it calls success(), fail(), or error().
    // Example Passport configuration
    import passport from 'passport';
    
    passport.use(strategy);
    
    // In your route handler
    app.get('/auth/callback', (req, res, next) => {
      passport.authenticate('magiclogin', (err, user, info) => {
        if (err) return next(err);
        if (!user) return res.redirect('/login?error=invalid-token');
        // User is authenticated
        req.logIn(user, () => res.redirect('/dashboard'));
      })(req, res, next);
    });
  8. Use MagicLoginStrategy.send to trigger magic links

    master

    The .send(req, res) method is used to initiate the magic link process. It handles payload extraction from either req.query (for GET) or req.body (for POST).

    Requirements:

    • If using POST, the Content-Type header must be application/json.
    • The payload must include a destination field (e.g., an email address).

    Behavior:

    1. Generates a random 5-digit code.
    2. Generates a JWT containing the payload and the code.
    3. Calls the configured sendMagicLink function.
    4. Returns a JSON response: { "success": true, "code": "12345" } on success, or { "success": false, "error": ... } on failure.
    // Example Express route to trigger magic link
    app.post('/auth/magic-link', (req, res) => {
      // req.body should be { "destination": "user@example.com" }
      strategy.send(req, res);
    });