Closed 1varunvc closed 1 day ago
This guide demonstrates how to implement Spotify authentication in your Node.js app using Passport.js and the passport-spotify
strategy, matching your actual implementation with separate files and added security features.
Navigate to your backend
directory and install the necessary packages:
npm install passport passport-spotify express-session axios express-rate-limit
passport
: Middleware for authenticationpassport-spotify
: Spotify OAuth 2.0 authentication strategy for Passportexpress-session
: Middleware for managing user sessionsaxios
: Promise-based HTTP clientexpress-rate-limit
: Middleware for rate limiting to enhance securityFollow the steps outlined in the original guide to obtain your Client ID and Client Secret, and set your Redirect URI.
Update your .env
file:
SPOTIFY_CLIENT_ID=your_spotify_client_id
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
SPOTIFY_REDIRECT_URI=http://localhost:3000/auth/spotify/callback
SESSION_SECRET=your_session_secret
NODE_ENV=development
In your server.js
, configure express-session with enhanced security settings.
// server.js
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const { router: authRoutes } = require('./auth'); // Adjust the path if necessary
const spotifyRoutes = require('./spotifyRoutes'); // Adjust the path if necessary
const rateLimit = require('express-rate-limit');
const app = express();
// Global rate limiter (e.g., max 1000 requests per 15 minutes per IP)
const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 1000, // limit each IP to 1000 requests per windowMs
message: {
error: 'Too many requests, please try again later.',
},
});
// Apply the global rate limiter to all requests
app.use(globalLimiter);
// Configure Express session
app.use(
session({
secret: process.env.SESSION_SECRET, // Ensure SESSION_SECRET is set in .env
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // Set to true in production
httpOnly: true, // Helps prevent XSS
maxAge: 24 * 60 * 60 * 1000, // 1 day
},
})
);
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
// Use authentication routes
app.use(authRoutes);
// Use Spotify data routes
app.use(spotifyRoutes);
// Define other routes
app.get('/', (req, res) => {
res.send('Welcome to the Snyder App Backend');
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
Notes:
express-rate-limit
to protect against DDoS attacks.cookie.secure
based on NODE_ENV
, added httpOnly
flag, and defined maxAge
for session cookies.server.js
Already included in the server.js
above:
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
auth.js
Create a new file auth.js
:
// auth.js
const express = require('express');
const passport = require('passport');
const SpotifyStrategy = require('passport-spotify').Strategy;
const axios = require('axios'); // Use axios for HTTP requests
const router = express.Router();
// Serialize and deserialize user instances to and from the session
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((obj, done) => {
done(null, obj);
});
// Configure the Spotify strategy for use by Passport
passport.use(
new SpotifyStrategy(
{
clientID: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
callbackURL: process.env.SPOTIFY_REDIRECT_URI,
},
(accessToken, refreshToken, expires_in, profile, done) => {
const user = {
spotifyId: profile.id,
displayName: profile.displayName,
photos: profile.photos,
accessToken: accessToken,
refreshToken: refreshToken,
expiresIn: Date.now() + expires_in * 1000, // Convert expires_in to timestamp
};
return done(null, user);
}
)
);
Notes:
axios
: Usedaxios
for HTTP requests instead of the traditional request
as the latter has been long deprecated.expiresIn
as a timestamp.auth.js
// Authentication route for Spotify
router.get(
'/auth/spotify',
passport.authenticate('spotify', {
scope: ['user-read-email', 'user-read-private'],
showDialog: true,
})
);
// Callback route
router.get(
'/auth/spotify/callback',
passport.authenticate('spotify', { failureRedirect: '/login' }),
(req, res) => {
// Successful authentication
res.redirect('/');
}
);
// Logout route
router.get('/logout', (req, res) => {
req.logout(() => {
res.redirect('/');
});
});
// Middleware to ensure user is authenticated
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.redirect('/login'); // Redirect unauthenticated users to login
}
// Export both the router and utility functions
module.exports = { router, ensureAuthenticated };
Notes:
express.Router()
for modularity.auth.js
// Function to refresh the Spotify access token
async function refreshSpotifyToken(refreshToken) {
const authOptions = {
method: 'post',
url: 'https://accounts.spotify.com/api/token',
params: {
grant_type: 'refresh_token',
refresh_token: refreshToken,
},
headers: {
Authorization:
'Basic ' +
Buffer.from(
process.env.SPOTIFY_CLIENT_ID + ':' + process.env.SPOTIFY_CLIENT_SECRET
).toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded',
},
};
try {
const response = await axios(authOptions);
const newAccessToken = response.data.access_token;
const newExpiresIn = Date.now() + response.data.expires_in * 1000;
return { newAccessToken, newExpiresIn };
} catch (error) {
console.error('Failed to refresh access token:', error.response.data);
throw error;
}
}
// Export the refresh function
module.exports = { router, ensureAuthenticated, refreshSpotifyToken };
spotifyRoutes.js
Create a new file spotifyRoutes.js
:
// spotifyRoutes.js
const express = require('express');
const axios = require('axios');
const rateLimit = require('express-rate-limit');
const { ensureAuthenticated, refreshSpotifyToken } = require('./auth'); // Adjust the path if necessary
const router = express.Router();
// Rate limiter middleware (e.g., max 100 requests per 15 minutes per IP)
const spotifyDataLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: {
error: 'Too many requests, please try again later.',
},
});
// Utility function to check token expiration
function isTokenExpired(expiresIn) {
return Date.now() > expiresIn;
}
// Function to fetch user data from Spotify
async function getSpotifyData(accessToken) {
const options = {
method: 'get',
url: 'https://api.spotify.com/v1/me',
headers: { Authorization: 'Bearer ' + accessToken },
};
const response = await axios(options);
return response.data;
}
// Route to fetch Spotify data
router.get(
'/api/spotify-data',
ensureAuthenticated,
spotifyDataLimiter, // Apply rate limiting to this route
async (req, res) => {
try {
// Check if token has expired
if (isTokenExpired(req.user.expiresIn)) {
// Refresh the access token
const { newAccessToken, newExpiresIn } = await refreshSpotifyToken(
req.user.refreshToken
);
// Update the user's access token and expiration time
req.user.accessToken = newAccessToken;
req.user.expiresIn = newExpiresIn;
req.session.passport.user = req.user; // Update the session
}
// Token is valid, proceed to make the API call
const spotifyData = await getSpotifyData(req.user.accessToken);
res.json(spotifyData);
} catch (error) {
console.error('Error fetching Spotify data:', error);
res.status(500).json({ error: 'Failed to fetch Spotify data' });
}
}
);
module.exports = router;
Notes:
axios
to interact with Spotify's API.server.js
to Use New RoutesAlready shown in step 3, but here's the relevant part:
// Use authentication routes
app.use(authRoutes);
// Use Spotify data routes
app.use(spotifyRoutes);
Already integrated into spotifyRoutes.js
:
isTokenExpired
function compares the current time with the token's expiration.refreshSpotifyToken
is called to obtain a new access token.secure
flag based on environment..env
.Install Dependencies:
npm install
Start Your Server:
node server.js
Visit the Login Route:
Open your browser and navigate to:
http://localhost:3000/auth/spotify
Authenticate with Spotify:
Access Protected Route:
/api/spotify-data
to fetch your Spotify user data./auth/spotify
to initiate authentication./logout
.passport.authenticate
to access additional Spotify features.By modularizing your code and incorporating security best practices, you've successfully implemented Spotify authentication in your Node.js app using Passport.js. This setup includes secure handling of user tokens, session management, rate limiting, and structured routes for authentication and accessing protected resources.