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
SKILL.md
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
- Use bcrypt for passwords - Never store plain text
- Secure session cookies - Set secure, httpOnly, sameSite
- Use Redis for sessions - In production with multiple servers
- Handle errors gracefully - Don't expose sensitive info
- Validate input - Before authentication
- Rate limit login attempts - Prevent brute force
References
Repository

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