Marketplace

passport

Implements Passport.js authentication middleware with local, OAuth, and JWT strategies for Express/Node.js. Use when building Node.js APIs, implementing custom auth flows, or needing flexible authentication strategies.

$ 설치

git clone https://github.com/mgd34msu/goodvibes-plugin /tmp/goodvibes-plugin && cp -r /tmp/goodvibes-plugin/plugins/goodvibes/skills/webdev/authentication/passport ~/.claude/skills/goodvibes-plugin

// tip: Run this command in your terminal to install the skill


name: passport description: Implements Passport.js authentication middleware with local, OAuth, and JWT strategies for Express/Node.js. Use when building Node.js APIs, implementing custom auth flows, or needing flexible authentication strategies.

Passport.js

Passport.js is authentication middleware for Node.js. It supports 500+ authentication strategies including local username/password, OAuth, JWT, and more.

Quick Start

npm install passport express-session
npm install passport-local  # For username/password

Basic Setup

// app.ts
import express from 'express'
import session from 'express-session'
import passport from 'passport'

const app = express()

app.use(express.json())
app.use(express.urlencoded({ extended: false }))

app.use(session({
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  }
}))

app.use(passport.initialize())
app.use(passport.session())

Local Strategy (Username/Password)

Configure Strategy

// config/passport.ts
import passport from 'passport'
import { Strategy as LocalStrategy } from 'passport-local'
import bcrypt from 'bcrypt'
import { getUserByEmail, getUserById } from './db'

passport.use(new LocalStrategy(
  {
    usernameField: 'email',
    passwordField: 'password'
  },
  async (email, password, done) => {
    try {
      const user = await getUserByEmail(email)

      if (!user) {
        return done(null, false, { message: 'Invalid email or password' })
      }

      const isValid = await bcrypt.compare(password, user.passwordHash)

      if (!isValid) {
        return done(null, false, { message: 'Invalid email or password' })
      }

      return done(null, user)
    } catch (error) {
      return done(error)
    }
  }
))

// Serialize user to session
passport.serializeUser((user: any, done) => {
  done(null, user.id)
})

// Deserialize user from session
passport.deserializeUser(async (id: string, done) => {
  try {
    const user = await getUserById(id)
    done(null, user)
  } catch (error) {
    done(error)
  }
})

Login Route

// routes/auth.ts
import express from 'express'
import passport from 'passport'

const router = express.Router()

router.post('/login', (req, res, next) => {
  passport.authenticate('local', (err, user, info) => {
    if (err) {
      return next(err)
    }

    if (!user) {
      return res.status(401).json({ error: info?.message || 'Login failed' })
    }

    req.logIn(user, (err) => {
      if (err) {
        return next(err)
      }
      return res.json({ user: { id: user.id, email: user.email } })
    })
  })(req, res, next)
})

router.post('/logout', (req, res, next) => {
  req.logout((err) => {
    if (err) {
      return next(err)
    }
    res.json({ message: 'Logged out' })
  })
})

router.get('/me', (req, res) => {
  if (!req.user) {
    return res.status(401).json({ error: 'Not authenticated' })
  }
  res.json({ user: req.user })
})

export default router

Protect Routes

Middleware

// middleware/auth.ts
import { Request, Response, NextFunction } from 'express'

export function isAuthenticated(req: Request, res: Response, next: NextFunction) {
  if (req.isAuthenticated()) {
    return next()
  }
  res.status(401).json({ error: 'Authentication required' })
}

export function hasRole(role: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.isAuthenticated()) {
      return res.status(401).json({ error: 'Authentication required' })
    }
    if ((req.user as any).role !== role) {
      return res.status(403).json({ error: 'Insufficient permissions' })
    }
    next()
  }
}

Usage

import { isAuthenticated, hasRole } from './middleware/auth'

// Protected route
app.get('/dashboard', isAuthenticated, (req, res) => {
  res.json({ data: 'Protected data' })
})

// Admin only
app.get('/admin', hasRole('admin'), (req, res) => {
  res.json({ data: 'Admin data' })
})

JWT Strategy

For stateless API authentication.

npm install passport-jwt jsonwebtoken
npm install -D @types/jsonwebtoken @types/passport-jwt

Configure JWT Strategy

// config/passport-jwt.ts
import passport from 'passport'
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
import { getUserById } from './db'

const options = {
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET!,
  algorithms: ['HS256'] as const
}

passport.use(new JwtStrategy(options, async (payload, done) => {
  try {
    const user = await getUserById(payload.sub)

    if (!user) {
      return done(null, false)
    }

    return done(null, user)
  } catch (error) {
    return done(error, false)
  }
}))

Issue JWT

import jwt from 'jsonwebtoken'

function generateToken(user: { id: string; email: string }) {
  return jwt.sign(
    { sub: user.id, email: user.email },
    process.env.JWT_SECRET!,
    { expiresIn: '7d' }
  )
}

router.post('/login', async (req, res, next) => {
  passport.authenticate('local', { session: false }, (err, user, info) => {
    if (err) return next(err)
    if (!user) return res.status(401).json({ error: info?.message })

    const token = generateToken(user)
    res.json({ token, user: { id: user.id, email: user.email } })
  })(req, res, next)
})

Protect Routes with JWT

router.get('/protected',
  passport.authenticate('jwt', { session: false }),
  (req, res) => {
    res.json({ user: req.user })
  }
)

OAuth Strategies

Google OAuth

npm install passport-google-oauth20
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'

passport.use(new GoogleStrategy({
    clientID: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    callbackURL: '/auth/google/callback',
    scope: ['email', 'profile']
  },
  async (accessToken, refreshToken, profile, done) => {
    try {
      // Find or create user
      let user = await getUserByGoogleId(profile.id)

      if (!user) {
        user = await createUser({
          googleId: profile.id,
          email: profile.emails?.[0].value,
          name: profile.displayName,
          avatar: profile.photos?.[0].value
        })
      }

      return done(null, user)
    } catch (error) {
      return done(error as Error)
    }
  }
))

OAuth Routes

// Start OAuth flow
router.get('/auth/google',
  passport.authenticate('google', { scope: ['email', 'profile'] })
)

// Handle callback
router.get('/auth/google/callback',
  passport.authenticate('google', {
    failureRedirect: '/login',
    successRedirect: '/dashboard'
  })
)

GitHub OAuth

npm install passport-github2
import { Strategy as GitHubStrategy } from 'passport-github2'

passport.use(new GitHubStrategy({
    clientID: process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    callbackURL: '/auth/github/callback'
  },
  async (accessToken, refreshToken, profile, done) => {
    try {
      let user = await getUserByGithubId(profile.id)

      if (!user) {
        user = await createUser({
          githubId: profile.id,
          email: profile.emails?.[0].value,
          name: profile.displayName || profile.username,
          avatar: profile.photos?.[0].value
        })
      }

      done(null, user)
    } catch (error) {
      done(error as Error)
    }
  }
))

router.get('/auth/github',
  passport.authenticate('github', { scope: ['user:email'] })
)

router.get('/auth/github/callback',
  passport.authenticate('github', { failureRedirect: '/login' }),
  (req, res) => {
    res.redirect('/dashboard')
  }
)

Multiple Strategies

Combined Local + OAuth

// User model supports multiple auth methods
interface User {
  id: string
  email: string
  passwordHash?: string
  googleId?: string
  githubId?: string
}

// Find user by any method
async function findOrCreateUser(profile: OAuthProfile) {
  // Check for existing user by email first
  let user = await getUserByEmail(profile.email)

  if (user) {
    // Link OAuth account to existing user
    await linkOAuthAccount(user.id, profile.provider, profile.id)
    return user
  }

  // Create new user
  return createUser({
    email: profile.email,
    [profile.provider + 'Id']: profile.id,
    name: profile.name
  })
}

Session Store

Redis Session Store

npm install connect-redis redis
import RedisStore from 'connect-redis'
import { createClient } from 'redis'

const redisClient = createClient({ url: process.env.REDIS_URL })
await redisClient.connect()

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    maxAge: 24 * 60 * 60 * 1000
  }
}))

Database Session Store

npm install connect-pg-simple
import pgSession from 'connect-pg-simple'
import { pool } from './db'

const PostgresStore = pgSession(session)

app.use(session({
  store: new PostgresStore({
    pool,
    tableName: 'sessions'
  }),
  secret: process.env.SESSION_SECRET!,
  resave: false,
  saveUninitialized: false
}))

TypeScript Types

// types/express.d.ts
declare global {
  namespace Express {
    interface User {
      id: string
      email: string
      name?: string
      role?: string
    }
  }
}

export {}

Error Handling

// Custom authentication error handler
router.post('/login', (req, res, next) => {
  passport.authenticate('local', (err, user, info) => {
    if (err) {
      console.error('Auth error:', err)
      return res.status(500).json({ error: 'Authentication failed' })
    }

    if (!user) {
      return res.status(401).json({
        error: info?.message || 'Invalid credentials',
        code: 'INVALID_CREDENTIALS'
      })
    }

    req.logIn(user, (err) => {
      if (err) {
        console.error('Session error:', err)
        return res.status(500).json({ error: 'Session creation failed' })
      }

      res.json({
        user: {
          id: user.id,
          email: user.email,
          name: user.name
        }
      })
    })
  })(req, res, next)
})

Testing

import request from 'supertest'
import app from './app'

describe('Authentication', () => {
  it('should login with valid credentials', async () => {
    const res = await request(app)
      .post('/auth/login')
      .send({ email: 'test@example.com', password: 'password' })
      .expect(200)

    expect(res.body.user.email).toBe('test@example.com')
  })

  it('should reject invalid credentials', async () => {
    await request(app)
      .post('/auth/login')
      .send({ email: 'test@example.com', password: 'wrong' })
      .expect(401)
  })

  it('should protect routes', async () => {
    await request(app)
      .get('/dashboard')
      .expect(401)
  })
})

Best Practices

  1. Use bcrypt for passwords - Never store plain text
  2. Secure session cookies - Set secure, httpOnly, sameSite
  3. Use Redis for sessions - In production with multiple servers
  4. Handle errors gracefully - Don't expose sensitive info
  5. Validate input - Before authentication
  6. Rate limit login attempts - Prevent brute force

References

Repository

mgd34msu
mgd34msu
Author
mgd34msu/goodvibes-plugin/plugins/goodvibes/skills/webdev/authentication/passport
0
Stars
0
Forks
Updated6h ago
Added1w ago