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.
2
Answers
That was very helpful, thanks. But I decided to do some restructuring, because other issues where popping up. But I still have the same Issue, that I don't get an accessToken because of a bad request 400.
The Errror in the Terminal is: WebapiAuthenticationError: An authentication error occurred while communicating with Spotify's Web API.
I triple checked my redirect URI and cliendID, but seems all fine.
This is my flow right now:
I use this main.jsx as entry point:
Then my Auth.jsx chcecks for a code and if non directs to my Login:
My Login is for now just a simple button which should direct the user to the Spotify Login:
And in my app I call the useAuth.js with the aquired code:
And my useAuth should handle getting an accessToken and refreshToken by directing to the server:
And this is how my server.js looks like now:
Problem is in using this wrongly
as the express work from top to down. so whatever req you made to server it runs from top to bottom. The way you setup middleware it runs before any req you made to server, so the authentication parts run before reaching to your actual req.
And I think you also can’t get to /login route in browser; it gives you unauthorized access (401) as setup. It’s because of that above so the simple solution to this is setup this middleware after defining /login route or any other route which doesn’t require authentication or use individually in each route (Whatever you preferred, search a little about this ,Thank you )