I’m working on my first react App right now, which uses the Spotify API. But I ran into troubles with the Authentication Process. Mainly the Problem is that my custom Hook useAuth, responsible for getting an accessToken is not connecting to my server.
My client runs on localhost:5173. My Server on localhost:3001.
My useAuth.js:
import { useState, useEffect } from 'react'
import axios from "axios";
const API_URL = 'http://localhost:3001';
export default function useAuth(code) {
const [accessToken, setAccessToken] = useState(null);
const [refreshToken, setRefreshToken] = useState(null);
const [expiresIn, setExpiresIn] = useState(null);
useEffect(() => {
if (!code) {
console.warn('No code provided, skipping authentication.');
return;
}
axios.post(`${API_URL}/login`, { code }, { withCredentials: true })
.then(res => {
console.log('Successfully obtained tokens:', res.data);
setAccessToken(res.data.accessToken);
setRefreshToken(res.data.refreshToken);
setExpiresIn(res.data.expiresIn);
window.history.pushState({}, null, '/');
})
.catch(err => {
console.error('Error during authentication:', err.response || err.message);
console.log(code);
alert('Failed to authenticate. Please log in again.');
window.location = '/';
});
}, [code]);
useEffect(() => {
if (!refreshToken || !expiresIn) return;
const interval = setInterval(() => {
axios
.post(`${API_URL}/refresh`, {
refreshToken,
})
.then(res => {
console.log('Token refreshed successfully:', res.data);
setAccessToken(res.data.accessToken);
setExpiresIn(res.data.expiresIn);
})
.catch(error => {
console.error('Error during token refresh:', error);
alert('Session expired. Please log in again.');
window.location = '/';
})
}, (expiresIn - 60) * 1000);
return () => clearInterval(interval);
}, [refreshToken, expiresIn])
return accessToken;
}
And my server.js:
import 'dotenv/config';
console.log('Process object:', process.env);
import express from 'express';
import SpotifyWebApi from 'spotify-web-api-node';
import cors from 'cors';
import bodyParser from 'body-parser';
import session from 'express-session';
console.log('SPOTIFY_CLIENT_ID:', process.env.SPOTIFY_CLIENT_ID);
console.log('SPOTIFY_CLIENT_SECRET:', process.env.SPOTIFY_CLIENT_SECRET);
console.log('REDIRECT_URI:', process.env.REDIRECT_URI);
const app = express();
const port = 3001;
const corsOptions = {
origin: 'http://localhost:5173',
credentials: true, // Allow credentials to be sent/received
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // Allowed HTTP methods
allowedHeaders: ['Content-Type', 'Authorization'], // Allowed headers
};
app.use(cors(corsOptions));
app.use(bodyParser.json());
app.use(express.static('public')); // If serving static files
app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;");
next();
});
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: {
secure: false, // Use 'true' in production with HTTPS
httpOnly: true, // Helps to prevent XSS attacks by making the cookie inaccessible via JavaScript
sameSite: 'None', // Allows cross-site requests with the cookie
}
}));
const spotifyApi = new SpotifyWebApi({
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
redirectUri: process.env.REDIRECT_URI,
});
app.use(async (req, res, next) => {
req.session = req.session || {};
if (!req.session.accessToken || req.session.tokenExpirationTimestampMs <= Date.now()) {
if (req.session.refreshToken) {
spotifyApi.setRefreshToken(req.session.refreshToken);
try {
const data = await spotifyApi.refreshAccessToken();
req.session.accessToken = data.body['access_token'];
req.session.tokenExpirationTimestampMs = Date.now() + (data.body['expires_in'] * 1000);
spotifyApi.setAccessToken(req.session.accessToken);
next(); // Continue to the next middleware or route
} catch (err) {
console.error('Could not refresh access token', err);
return res.status(401).json({ error: 'Unauthorized. Please log in again.' });
}
} else {
return res.status(401).json({ error: 'Unauthorized. Please log in again.' });
}
} else {
spotifyApi.setAccessToken(req.session.accessToken);
next(); // Continue to the next middleware or route
}
});
app.post('/login', async (req, res) => {
const code = req.body.code;
try {
const data = await spotifyApi.authorizationCodeGrant(code);
res.json({
accessToken: data.body.access_token,
refreshToken: data.body.refresh_token,
expiresIn: data.body.expires_in,
});
} catch (err) {
console.error('Spotify authorization error:', err); // Log the detailed error
res.status(401).json({ error: 'Unauthorized. Please log in again.' });
}
});
app.post('/refresh', async (req, res) => {
const refreshToken = req.body.refreshToken;
try {
spotifyApi.setRefreshToken(refreshToken);
const data = await spotifyApi.refreshAccessToken();
req.session.accessToken = data.body.access_token;
req.session.tokenExpirationTimestampMs = Date.now() + (data.body.expires_in * 1000);
res.json({
accessToken: data.body.access_token,
expiresIn: data.body.expires_in,
});
} catch (err) {
console.error('Error in /refresh', err);
res.status(400).json({ error: 'Failed to refresh token. Please log in again.' });
}
});
app.get('/api/userSubscriptionLevel', async (req, res) => {
try {
const user = await spotifyApi.getMe();
const product = user.body.product || 'free'; // Default to 'free' if no product found
res.json({ product });
} catch (error) {
console.error('Error in /api/userSubscriptionLevel:', error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
app.get('/api/getAccessToken', async (req, res) => {
try {
const accessToken = spotifyApi.getAccessToken();
console.log('Access token:', accessToken);
res.json({ accessToken });
} catch (error) {
console.error('Error in /api/getAccessToken', error);
res.status(500).json({ error: 'Internal Server Error' });
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
I tryied to solve the issue with chat-gpt, which obviously did’t work out to well. I might have overcomplicated things at some point.