I am trying to do Spotify Authentication using Client(React) and Server, the logging in works for a second then the page refreshes immediately after logging in and logs the user out. Anyone knows where might be the problem?
Here is my code:
server.js:
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const SpotifyWebApi = require('spotify-web-api-node');
const app = express();
app.use(cors()) // to handle the cross-origin requests
app.use(express.json()); // to parse JSON bodies
const port = process.env.PORT || 8000;
const credentials = {
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
redirectUri: process.env.REDIRECT_URI || "http://localhost:3000"
};
app.post('/refresh', (req, res) => {
const refreshToken = req.body.refreshToken;
// console.log("Hii");
let spotifyApi = new spotifyWebApi({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
redirectUri: process.env.REDIRECT_URI,
refreshToken,
});
spotifyApi
.refreshAccessToken()
.then((data) => {
// console.log(data.body);
res.json({
accessToken: data.body.access_token,
expiresIn: data.body.expires_in,
})
})
.catch((err) => {
console.log(err);
res.sendStatus(400);
});
});
app.post('/login', (req,res) => {
// Get the "code" value posted from the client-side and get the user data from the spotify api
let spotifyApi = new spotifyWebApi(credentials)
const code = req.body.code
spotifyApi.authorizationCodeGrant(code).then((data) => {
// Returning the User's Data in the json formate
res.json({
accessToken : data.body.access_token,
refreshToken : data.body.refresh_token,
expiresIn : data.body.expires_in
})
})
.catch((err) => {
console.log(err);
res.sendStatus(400)
})
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Client side:
useAuth.js:
import React from 'react';
import { useEffect, useState } from 'react';
import axios from "axios"
export default function useAuth(code) {
const [accessToken, setAccessToken] = useState();
const [refreshToken, setRefreshToken] = useState();
const [expiresIn, setExpiresIn] = useState();
useEffect(() => {
axios
.post("/login", {code})
.then((res) => {
window.history.pushState({}, null, "/");
console.log(res.data);
setAccessToken(res.data.accessToken);
setRefreshToken(res.data.refreshToken);
setExpiresIn(res.data.expiresIn);
})
.catch(() => {
window.location = "/";
});
}, [code]);
useEffect(() => {
if (!refreshToken || !expiresIn) {
return;
}
let interval = setInterval(() => {
axios
.post("/refresh", {refreshToken})
.then((res) => {
setAccessToken(res.data.accessToken);
setExpiresIn(res.data.expiresIn);
})
.catch(() => {
window.location = "/";
});
}, (expiresIn - 60) * 1000);
return () => clearInterval(interval)
}, [refreshToken, expiresIn]);
return accessToken;
}
spotifyConfig.js:
const authEndpoint = "https://accounts.spotify.com/authorize";
const redirectUri = "http://localhost:3000";
const clientId = "ea28d4ba34f34b44b59c640052c6e098";
const scopes = [
"streaming",
"playlist-modify-public",
"ugc-image-upload",
"user-read-email",
"user-read-private",
"user-read-currently-playing",
"user-read-recently-played",
"user-read-playback-state",
"user-modify-playback-state"
];
export const loginUrl = `${authEndpoint}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scopes.join(
"%20"
)}&show_dialog=true`;
App.js:
import './App.css';
import Dashboard from './pages/Dashboard';
import Login from './components/Login';
const code = new URLSearchParams(window.location.search).get('code')
function App() {
return (
<div>
{code ? <Dashboard code={code}/> : <Login/>}
</div>
);
}
export default App;
👉🏽 this page appears a second then logs out Dashboard.js:
import React, {useEffect} from 'react';
import useAuth from '../useAuth';
import SpotifyWebApi from "spotify-web-api-node";
const spotifyApi = new SpotifyWebApi({
clientId: "ea28d4ba34f34b44b59c640052c6e098",
});
export default function Dashboard({code}) {
const accessToken = useAuth(code);
useEffect(() => {
if (!accessToken) return;
spotifyApi.setAccessToken(accessToken);
spotifyApi.getMe().then(data => {
console.log(data);
})
}, []);
return (
<div>
This is the home page 🏠
</div>
)
}
Login.js:
import React from 'react';
import { loginUrl } from '../spotifyConfig';
export default function Login() {
return (
<div>
<a href={loginUrl}>
<button>LOGIN WITH SPOTIFY</button>
</a>
<div className="links">
<p>
⚠ When joining or creating a Queue, open Spotify to be able to queue up tracks
</p>
</div>
</div>
)
}
I experienced a similar issue. Here are a few steps that helped me to resolve it and many of the subsequent issues I encountered.
Run your IDE's debugger and set break points for your /login request. Also, check whether your environment variables are getting set as you intend (if running VSCode you can learn how to set this up here). In particular, make sure your credentials
clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, redirectUri: process.env.REDIRECT_URI
are correct. If your environment variables are not being loaded you won't be able to create a new SpotifyWebApi instance (check out dotenv file is not loading environment variables). Another easy way to check if this is the problem is to hard code your values temporarily.
Test your server /login endpoint independently before running the client to see whether the endpoint is returning 400 or another error when executing requests to Spotify.
Make sure you keep your devtools console open in your browser so you can identify any failed requests you're making to the Spotify API and consider setting devtools to preserve logs in case the page refreshing is deleting them.
If you are running both your client and server from inside VSCode, try instead running them both in new shells outside of VSCode.
I was having a similar issue, having followed this tutorial, and then this YouTube tutorial.
What I realised was that my App component was being rendered twice, which was causing everything to be called twice, including the login endpoint. I was able to verify this using console.log in the endpoint and seeing if the log appeared twice. As the same Spotify code was being used twice in SpotifyWebApi.authorizationCodeGrant, this was what was causing the error.
I was able to trace the issue to the React.StrictMode being enabled, which must have happened when using the create-react-app command. Verify whether the tags appear in your index.js file. For more information, check this StackOverflow answer: My React Component is rendering twice because of Strict Mode
Related
I am currently having an issue with my React Application not working on any other browser other than Chrome. The Javascript loads just fine with no errors on Chrome and the application is currently fully deployed on Heroku (link: https://weathrd.herokuapp.com/).
In regards to my application, I have a search query set up in the "overview.js" component that creates a "get" request, with a parameter passed in that gets fed into the weather api I am using. Then, I retrieve the json information from the "/forecast" page and feed that back into "overview.js" to display on the screen.
I do not have any regex notation within any of my code, so I don't think that would be an issue here. I also have fully updated my Heroku deploy code and I do not think there is some sort of confusion on Heroku? Regardless, here is my server code, overview component code, and the error I am receiving on Safari:
server code:
const PORT = process.env.PORT || 8000;
const path = require('path');
const express = require('express');
const cors = require('cors');
const axios = require('axios');
require('dotenv').config();
const app = express();
app.use(cors());
app.use(express.static("public"))
app.get('/', (req, res) => {
res.json('hi');
});
app.get('/forecast', (req, res) => {
const options = {
method: 'GET',
url: `http://api.weatherapi.com/v1/forecast.json?`,
params: {
q: req.query.city,
key : process.env.REACT_APP_API_KEY,
days: '3',
api: 'no',
alerts: 'no',
},
};
axios.request(options).then((response) => {
res.json(response.data);
}).catch((error) => {
console.log(error);
});
});
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT} `))
Safari Error:
The error also mentions the component from which I am making the API request from "overview.js", so here is that code also:
overview.js
import React, {useState} from 'react';
import './overview.css';
import { RecentSearches } from '../Recent Searches/recentSearches';
import { Hourly } from '../Hourly/hourly';
import { Fiveday } from '../5 Day Forecast/fiveday';
import 'animate.css';
const axios = require('axios');
export function Overview() {
const [forecast, setForecast] = useState(null);
// this callback function receives the searched city entered from recentSearches and applies it to fetchForecast
const getSearch = (searchedCity) => {
fetchForecast(searchedCity);
};
async function fetchForecast(searchedCity) {
const options = {
method: 'GET',
url: 'https://weathrd.herokuapp.com/forecast',
params: {city: searchedCity}
};
axios.request(options).then((response) => {
console.log(response.data);
setForecast(response.data);
}).catch((error) => {
console.log(error);
})
};
return (
<div>
<div className='jumbotron' id='heading-title'>
<h1>Welcome to <strong>Weathered</strong>!</h1>
<h3>A Simple Weather Dashboard </h3>
</div>
<div className='container-fluid' id='homepage-skeleton'>
<div className='d-flex' id='center-page'>
<RecentSearches getSearch={getSearch}/>
<Hourly forecast={forecast}/>
</div>
</div>
<Fiveday forecast={forecast}/>
</div>
)
};
Thanks for any assistance!
the problem i'm having is basically my app works fine in local but in production anywhere that i've used server side rendering returns 500 internal server error. the other parts of my site which are called normally like in useEffect or componentDidMount work completely fine, like my dashboard or authorization process works without a problem, but anywhere that i have used ssr returns 500.
Below is some examples of how i have handled my ssr pages.
index page:
import React from 'react';
import HomePage from '../components/homePage/index'
import { Api, GuestHeaders } from '../components/config'
const Home = (props) => {
return <HomePage {...props} />
}
export async function getServerSideProps() {
const Response = await Api.get(`/v1/index`, { headers: GuestHeaders })
return {
props: {
Detail: Response.data,
}
}
}
export default Home
here is my Api component:
import axios from 'axios';
const GuestHeaders = {
'Authorization': "",
'content-type': 'application/json'
}
const Api = axios.create({
baseURL: 'baseUrl'
})
export { Api, GuestHeaders };
here is my server.js:
// server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
// Be sure to pass `true` as the second argument to `url.parse`.
// This tells it to parse the query portion of the URL.
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
}).listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
and my next.config.js:
module.exports = {
basePath: '',
trailingSlash: false,
}
I have a React app which is using auth0 along side an express API server.
My question is, I can get the user information in the client side from the Auth0 user object, but i'm not sure how to access it within the api when a secure end point is called.
Sending the information to the api with any requests seems much less secure than using the access token somehow in the backend but i'm unsure how to do it or if it's even possible.
API SERVER
const express = require('express')
const app = express()
const port = 8000
const jwt = require('express-jwt')
const cors = require('cors')
var jwtCheck = jwt({
secret: '',
audience: 'http://localhost:8000',
issuer: 'https://dev-ml4yg9zg.us.auth0.com/',
algorithms: ['HS256']
});
app.use(cors())
app.get('/unprotected',(req,res) =>{
res.send("not secured resource")
})
app.get('/authed', jwtCheck,(req,res) =>{
// GET THE DATA FOR THE LOGGED IN USER WHO MADE THE CALL
res.send("secured resource")
})
app.listen(port, () =>{
console.log(`app listening on port ${port}`)
})
REACT APP
import React,{useEffect, useState} from 'react';
import axios from 'axios'
import {useAuth0} from '#auth0/auth0-react'
function App() {
const [accessToken, setAccessToken] = useState(null)
const [userMetaData, setUserMetadata] = useState(null)
const {
loginWithRedirect,
logout,
user,
isAuthenticated,
isLoading,
getAccessTokenSilently
} = useAuth0()
console.log(user)
const getToken = async () => {
try {
const accessToken = await getAccessTokenSilently({
audience: `http://localhost:8000`,
scope: "read:current_user",
});
setAccessToken(accessToken)
} catch (e) {
console.log(e.message);
}
};
const callProtected = () =>{
axios.get('http://localhost:8000/authed',{
headers:{
Authorization:`Bearer ${accessToken}`
}
}).then(res =>{
console.log(res.data)
}).catch(e =>{
console.log(e)
})
}
const callUnprotected = ()=>{
axios.get('http://localhost:8000/unprotected')
.then(res =>{
console.log(res.data)
}).catch(e =>{
console.log(e)
})
}
return (
<div className="App">
<button onClick={() => loginWithRedirect()}>Login</button>
<button onClick={() => logout({returnTo:window.location.origin})}>Log out</button>
<button onClick={() => getToken()}>Get token</button>
<button onClick={() => callUnprotected()}>Call unprotected resource</button>
<button onClick={() => callProtected()}>Call protected resource</button>
<div>
User : {user?.name}
</div>
</div>
);
}
export default App;
So I wasn't aware of the User Management API
To solve the issue of getting the user data to confirm authentication within my API server I decoded the JWT token server-side which contained the user ID, then I used the ID within a call to the management API to get the full user data.
The auth token for the call to the management API can be generated within the API dashboard on the Auth0 website
app.get('/authed', jwtCheck, async (req,res) =>{
let token = req.headers.authorization.substring(7, req.headers.authorization.length)
// GET THE DATA FOR THE LOGGED IN USER WHO MADE THE CALL
var decoded = jwt_decode(token);
console.log(decoded.sub)
axios.get(`https://************.us.auth0.com/api/v2/users/${decoded.sub}`,{
headers:{
Authorization:`Bearer *`,
}
}).then((res) =>{
console.log(res)
}).catch(e =>{
console.log(e)
})
res.send("secured resource")
})
I use gRPC but I have a problem initializing the service in Next.js app.
Goal: Create client service only once in app and use it in getServerSideProps (app doesn't use client-side routing).
For example, we have a service generated with grpc-tools (only available on SSR) and then I just want to initialize it somewhere. At first I thought it can be realized in a custom server.js:
const { credentials } = require('#grpc/grpc-js');
const express = require("express");
const next = require("next");
const { MyserviceClient } = require('./gen/myservice_grpc_pb');
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
// Init & Export
exports.myService = new MyserviceClient(
'http://localhost:3000',
credentials.createInsecure(),
);
(async () => {
await app.prepare();
const server = express();
server.get("*", (req, res) => handle(req, res));
server.listen(process.env.PORT, () => {
console.log(`Listening at http://localhost:${process.env.PORT}`);
});
})();
And then use it on the homepage, for example:
import React from 'react';
const { GetSmthRequest } = require('../gen/myservice_pb');
const { myService } = require('../server.js');
const IndexPage = () => (
<div>
<span>My HomePage</span>
</div>
)
const getServerSideProps = async () => {
const request = new GetSmthRequest();
request.setSomeStuff('random');
myService.getStmh(GetSmthRequest, (err, res) => {
//...
})
return {
props: {
}
}
}
export default IndexPage;
But for some reason it's not possible to initialize the client service in the server.js.
Also I tried doing it with next.config.js:
const { credentials } = require('#grpc/grpc-js');
const { MyserviceClient } = require('./gen/myservice_grpc_pb');
module.exports = {
serverRuntimeConfig: {
myService: new MyserviceClient(
'http://localhost:3000',
credentials.createInsecure(),
),
},
};
This solution works, so I can use the service through serverRuntimeConfig, thereby initializing it only once in the entire application, but when I make a request somewhere using getServerSideProps, I get an error:
Request message serialization failure: Expected argument of type ...
Error explanation: (https://stackoverflow.com/a/50845069/9464680)
That error message indicates that message serialization
(transformation of the message object passed to gRPC into binary data)
failed. This generally happens because the message object doesn't
match the expected message type or is otherwise invalid
Does anyone know why I am getting this error?
It's also interesting to see some examples of using Next.js with grpc-node.
For such a case you can use Node.js global
The react-google-login from the client react app sends the response back to the Nodejs server with a post request-
client code -
import axios from 'axios';
import React, { Component } from 'react';
import GoogleLogin from 'react-google-login';
import refreshTokenSetup from '../../utils/refreshToken';
const clientId =
'xxxxxx-xfdgsdjg3gfxxxxxxxxxxx.apps.googleusercontent.com';
function Login() {
const onSuccess = (res) => {
console.log('Login Success: currentUser:', res.profileObj);
alert(
`Logged in successfully welcome ${res.profileObj.name} 😍. \n See console for full profile object.`
);
axios
.post('http://localhost:5000/auth/checkToken', { body: res.tokenId })
.then()
.catch((err) => {
console.log(err);
});
};
const onFailure = (res) => {
console.log('Login failed: res:', res);
alert(
`Failed to login. 😢 Please ping this to repo owner twitter.com/sivanesh_fiz`
);
};
return (
<div>
<GoogleLogin
clientId={clientId}
buttonText='Login'
onSuccess={onSuccess}
onFailure={onFailure}
cookiePolicy={'single_host_origin'}
style={{ marginTop: '100px' }}
isSignedIn={true}
/>
</div>
);
}
export default Login;
the backend route-
const { OAuth2Client } = require('google-auth-library');
const key = require('../config/key');
module.exports = {
checkToken: (req, res, next) => {
console.log('checking begins...', req.body);
const client = new OAuth2Client(key.GOOGLE_CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: req.body,
audience: key.GOOGLE_CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
},
};
The above code is in reference to official Google Documentation available at- https://developers.google.com/identity/sign-in/web/backend-auth
Now everything works fine, user is signed in in the client side, the tokenId is sent back to the server and can be verified by console logging it, even on https://jwt.io/ but the following error is shown-
TypeError: jwt.split is not a function
at OAuth2Client.verifySignedJwtWithCertsAsync (E:\Projects\EAbackend\node_modules\google-auth-library\build\src\auth\oauth2client.js:528:30)
at OAuth2Client.verifyIdTokenAsync (E:\Projects\EAbackend\node_modules\google-auth-library\build\src\auth\oauth2client.js:394:34)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at async verify (E:\Projects\EAbackend\middleware\auth.js:9:22)
The main issue is that the example from Google doesn't really tell us what is expected as input to the verifyIdToken({options}) function.
This is what Google stated:
After Google returns an ID token, it's submitted by an HTTP POST method request, with the parameter name credential, to your login endpoint.
Which to me, is a little unclear of what is actually sent to the server to be verified. So here is a working example, copied/pasted from https://developers.google.com/identity/gsi/web/guides/verify-google-id-token, but with proper definitions for token and CLIENT_ID that are not mentioned on the Google site.
Server side Node JS:
exports.googleTokenChecker = (request, response) => {
const CLIENT_ID = request.body.clientId;
const token = request.body.credential;
// copied from Google example
const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const userid = payload['sub'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
}
Client side HTML to show what is sent to the backend:
<div id="g_id_onload"
data-client_id="CLIENT_ID.apps.googleusercontent.com"
data-callback="handleCredentialResponse"
data-auto_prompt="false">
</div>
<script>
function handleCredentialResponse(response) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:3000/api/google_token_checker", true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(response));
}
</script>
// contents of response parameter
// {
// clientId: 'CLIENT_ID.apps.googleusercontent.com',
// credential: 'JWT_HEADER.JWT_PAYLOAD.JWT_SIGNATURE',
// select_by: 'btn'
// }
Problem is in the idToken: req.body,
req.body has a body object in which the token was present, simply changing it to req.body.body solved the error.
The problem might be very begginner level but took a lot of my time and no online resourse was available which could point me in any direction.
Check the POST Request you will find the error.