Getting authorized access error from Spotify with spotify-wrapper - javascript

I'm trying to use the Spotify wrapper to use my app, I manage to hit Spotify's Account Service and the page to authorize the app to use a users spotify account information but I get an internal 500 server error:
index.js:6 POST https://accounts.spotify.com/en/authorize/accept 500 (Internal Server Error)
// Set up Spotify API wrapper
const scopes = ['user-read-private', 'user-read-email'];
const STATE_KEY = 'spotify_auth_state';
app.get('/login', (req, res) => {
const state = generateRandomString(16);
res.cookie(STATE_KEY, state);
res.redirect(spotifyApi.createAuthorizeURL(scopes, state));
});
app.get('/callback', (req, res) => {
const { code, state } = req.query;
const storedState = req.cookies ? req.cookies[STATE_KEY] : null;
if (state === null || state !== storedState) {
res.redirect('/#/error/state mismatch');
} else {
res.clearCookie(STATE_KEY);
spotifyApi
.authorizationCodeGrant(code)
.then(data => {
const { expires_in, access_token, refresh_token } = data.body;
// Set the access token on the API object to use it in later calls
spotifyApi.setAccessToken(access_token);
spotifyApi.setRefreshToken(refresh_token);
spotifyApi.getMe().then(({ body }) => {
console.log(body);
});
res.redirect('/search');
})
.catch(err => {
res.redirect('/#/error/invalid token');
});
}
});
This is my endpoint on that page:
https://accounts.spotify.com/en/authorize?client_id=CLIENT_ID&response_type=code&redirect_uri=http:%2F%2Flocalhost:8888%2Fcallback%2F&scope=user-read-private%20user-read-email&state=k4ogwe4h53i00000
Not entirely sure what's happening. I thought it was initially a state mismatch but the state generated is fine. Having trouble pinpointing where the error is. Anyone have any suggestions?

You need to set your redirect URI in the developer dashboard, ensuring that it's exactly the same as the one you're using in your app.
We're aware of the poor error handling for the invalid redirect URI error and it's being fixed.

Related

Getting error on console despite handling it (MERN + Redux)

I am sending axios get request whose end-point sends the user associated with the token stored in localStorage and then the redux state is updated with the user. When I don't have a token the end-point return a res with status 401 with message "Unauthorized" and then I handle it in the catch statement and set the "error" redux state. But even after doing this the error is displayed on the console like this:
Failed to load resource: the server responded with a status of 401 (Unauthorized) /users/auth:1
This is the function which makes api call and authorizes the user:
export function loadUser(){
return function (dispatch,getState){
dispatch(userLoading());
const token = getState().auth.token;
const config = {
headers:{
'Content-Type':'application/json'
}
}
if(token) config.headers['auth-token']=token;
axios.get('http://localhost:80/users/auth',config)
.then(user => {
dispatch(clearError())
dispatch(userLoaded(user.data))
})
.catch(error => {
dispatch(setError(error.response.status,error.response.data.msg));
dispatch(authError());
})
}
}
This is the middleware which handles the token before hitting the endpoint (In my case response is returned from here itself since there is no token sent):
function auth(req,res,next){
const token = req.header('auth-token');
if(!token) res.status(401).json({msg:"Unauthorized"})
else{
try{
const decoded = jwt.verify(token,jwt_secret);
req.user = decoded;
next();
}
catch(e){
res.status(400).json({msg:"Invalid token"})
}
}
}
I'm not able to figure out why am I getting error on console (State is getting updated as desired)
It is actually impossible to do with JavaScript. because of security concerns and a potential for a script to hide its activity from the user.
The best you can do is clearing them from your console.
console.clear();
I think it is because you are not getting the token when consulting your API.
If this is the case I recommend you use defaults.headers.common in this way
const axiosApi = axios.create({ baseURL: "http://localhost:80" });
const headerAuth = () => {
const token = getMyToken();
if (token) {
axiosApi.defaults.headers.common["Authorization"] = `Bearer ${token}`;
} else {
delete axiosApi.defaults.headers.common.Authorization;
}
};
export function loadUser(){
headerAuth(); // <-----
return function (dispatch,getState){
dispatch(userLoading());
axiosApi.get('/users/auth',config)
.then(user => {
dispatch(clearError())
dispatch(userLoaded(user.data))
})
.catch(error => {
dispatch(setError(error.response.status,error.response.data.msg));
dispatch(authError());
})
}
I recommend that you do not store the token in the REDUX but in sessionStorage

login user data does not persist once I refresh

Hi I am using express for backend authentication and these are my sign in functions/controllers on the front end.
export const signInUser = async credentials => {
console.log('this is for the signInUser', credentials)
try {
const resp = await api.post('/sign-in', credentials)
localStorage.setItem('token', resp.data.token)
return resp.data
} catch (error) {
throw error
}
}
onSignIn = event => {
event.preventDefault()
const { history, setUser } = this.props
signInUser(this.state)
.then(res => setUser(res.user))
.then(() => history.push('/Home'))
.catch(error => {
console.error(error)
this.setState({
loginUsername: '',
loginPassword: '',
})
})
}
setUser = user => this.setState({ user })
and this is my sign in controller on the backend
const signIn = async (req, res) => {
try {
console.log('hello' ,req.body);
const { loginUsername, username, loginPassword } = req.body;
const user = await User.findOne({
where: {
username: loginUsername
}
});
console.log('this is the user', user)
if (await bcrypt.compare(loginPassword, user.dataValues.password_digest)) {
const payload = {
id: user.id,
username: user.username,
password: user.password
};
const token = jwt.sign(payload, TOKEN_KEY);
return res.status(201).json({ user, token });
} else {
res.status(401).send("Username or Password is invalid- try again.");
}
} catch (error) {
return res.status(500).json({ error: error.message });
}
};
The issue is the state of the user doesn't persist on refresh but I still have the json webtoken in my local storage and this is an issue when I make post requests and even signing up since I am redirecting to the home page and losing the user state. Any help would be appreciated!
From your tags, I noticed that you are using React, so the solution is simple!
you can have an GlobalAuthManager context for your application that would wrap all the components at the most higher level! after <React.strictMode> like below:
<React.StrictMode>
<GlobalAuthManager.Provider value={{authData}}>
<App />
</GlobalAuthManager.Provider>
</React.StrictMode>
As you might guess, this would be a context! that would provide you your user data to all your components!
The Pattern:
1. Store token:
when your user logins to your app, you would receive a token ( in your response or in response header ), you need to store the token value in localstorage, or more better in cookie storage (there are a lot of articles about it why), one is here.
2. have a /getUserData endpoint in backend:
you need to have a /getUserData endpoint in backend to retrive your user data based on token
3. call /getUserData in app mount:
before every thing in your app, you need to call this endpoint if you find token in localstorage or cookie storage. so if you run this in your componnetDidMount or useEffect(() => { ... }, []), that would work!
4. store your user data and state in context:
after you've called the /getUserData and if you had a valid token(i mean not expired token or not interrupted and edited token) , you will get you user data and what you need to do is that you need to store this in your GlobalAuthManager and provide that in to your Global App component!
after that you have your user data available to you that you can decide to show login or sign up button in your Navbar or disable/enable comment section for example based on your user data!
Wrap up:
So the key is that you have to have a GlobalAuthManager for only one purpose, that before every thing it runs in the top level in your app and gets you your user data based on provided token from localstorage or cookie storage!
after that you can manage your app state based on that your user is logged in or not!

Angular 10 : ngOnInit two promises

I'm currently trying to implement in my Angular app the connection to Strava API.
To resume quickly:
User clicks on a button to connect to Strava
It is redirected to Strava for authentication(using PKCE)
Strava redirects to my app with a code
In the ngoninit I'm checking for route params and if I have the code, I launch two promises chained: the first one to get the Access Token from Strava then the recording into a DB(Firebase).
The problem is that sometimes the data is recorded in firebase and sometimes it is not. The behavior is not systematic. Strange thing is that I go into my postNewToken everytime because the console logs it.
If I just record to firebase (without strava token request) in ngOnInit(), it is created in 100% of the cases.
If I have a button that launches the token request and record into firebase, it seems to work everytime.
I have no idea how to solve it. It seems more a question of chaining promises into ngOnInit but I have no idea even how to bypass it.
The code from my component:
ngOnInit() {
const stravaCode = this.route.snapshot.queryParamMap.get('code');
if (stravaCode !== undefined) {
this.stravaService.handleStravaAuthorizationCode(stravaCode);
}
And in the service associated:
// Handle Strava Code received
handleStravaAuthorizationCode(authorizationCode: string) {
this.getStravaToken(authorizationCode).then(res => {
this.postNewToken(res).then(res => {
this.router.navigate(['encoding']);
});
});
}
// Get Strava access token to make the requests to the API -> only done once
getStravaToken(authorizationCode: string){
if (authorizationCode !== undefined){
console.log('Authorization code: ' + authorizationCode);
const data = {
client_id: environment.strava.client_id,
client_secret: environment.strava.client_secret,
code: authorizationCode,
grant_type: 'authorization_code'
};
return this.http.post<StravaToken>(this.stravaTokenURL, data)
.pipe(catchError(this.errorService.handleHttpError)).toPromise();
}
}
postNewToken(stravaToken: StravaToken) {
if (this.authService.isLoggedIn) {
console.log('Recording strava token into Firebase');
console.log(stravaToken);
return this.afs.collection('strava_tokens')
.add(stravaToken).then(res => console.log(res), err => console.log(err));
} else {
return Promise.reject(new Error('No User Logged In!'));
}
}
Finally, I understood.
I simply was not waiting for the connection to firebase to be established. So, I could not post any new data because I was not authenticated.

Any data passed to a Firebase Function returns Undefined

I've made a firebase function which every time I pass data to it and try to use the data, it returns that the data is undefined. This is the function I made:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Cloud Firestore.
const admin = require('firebase-admin');
// CORS Express middleware to enable CORS Requests.
const cors = require('cors')({origin: true});
admin.initializeApp();
exports.addUser = functions.https.onRequest((req, res) => {
const handleError = (error) => {
console.log('Error creating new user:', error);
//sends back that we've been unable to add the user with error
return res.status(500).json({
error: err,
});
}
try {
return cors(req, res, async () => {
console.log(req);
const uid = req.uid;
const dob = req.dob;
const postcode = req.postcode;
const sex = req.sex;
const username = req.username;
admin.firestore().collection('users').doc(uid).set({
dob:dob,
postcode:postcode,
sex:sex,
username:username,
})
.then(function(userRecord) {
console.log('Successfully created new user:', userRecord.username);
// Send back a message that we've succesfully added a user
return res.status(201).json({
message: 'User stored',
id: req.body.uid,
});
})
.catch(function(error) {
return handleError(error);
});
});
} catch (error) {
return handleError(error);
}
});
This is how I call it within react:
const addUserFunc = firebase.functions().httpsCallable('addUser');
console.log("Calling user func " + user.uid)
addUserFunc({
uid:user.uid,
dob:dob,
postcode:postcode,
sex:sex,
username:username,
}).then(function(result) {
console.log(result);
}).catch(err => {
console.log(err)
setErrors(prev => ([...prev, err.message]))
});
I've printed the data before sending the request and it definitely exists. I've also tried getting it within the function using req.body and req.query but this just returns the same.
This is the error I get in the firebase function logs:
Error: Value for argument "document path" is not a valid resource path. The path must be a non-empty string.
at Object.validateResourcePath (/srv/node_modules/#google-cloud/firestore/build/src/path.js:406:15)
at CollectionReference.doc (/srv/node_modules/#google-cloud/firestore/build/src/reference.js:1982:20)
at cors (/srv/index.js:44:51)
at cors (/srv/node_modules/cors/lib/index.js:188:7)
at /srv/node_modules/cors/lib/index.js:224:17
at originCallback (/srv/node_modules/cors/lib/index.js:214:15)
at /srv/node_modules/cors/lib/index.js:219:13
at optionsCallback (/srv/node_modules/cors/lib/index.js:199:9)
at corsMiddleware (/srv/node_modules/cors/lib/index.js:204:7)
at exports.addUser.functions.https.onRequest (/srv/index.js:31:16)
This is the error return in the web console for the react app:
Access to fetch at 'https://***/addUser' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I tested the function within using the emulator and passing the values using the link which works there but just not when deployed.
Any help would be great.
Your Cloud Function is defined as a HTTPS Function, which means that you can access it over a URL, but then you're calling it from your code as a Callable Function. The two types are different and not interchangeable.
If you want to use the firebase.functions().httpsCallable('addUser'); in your client code, you'll have to modify your Cloud Function to be a Callable Function too. This mostly means that you get the parameters from data instead of res, and return responses instead of sending them through res.
exports.addUser = functions.https.onCall((data, context) => {
...
const uid = context.auth.uid; // automatically passed to Callable Functions
return admin.firestore().collection('users').doc(uid).set({
dob: data.dob,
postcode: data.postcode,
sex: data.sex,
username: data.username,
})
.then(function(userRecord) {
return {
message: 'User stored',
id: req.body.uid,
};
}).catch(err => {
throw new functions.https.HttpsError('dabase-error', error);
})
});
Alternatively, you can leave your Cloud Function as is and instead modify the calling code to use something like fetch().

JWT Authorization with Axios and Vue.js (Header)

I'm still pretty new to web development, so I apologize in advance if the solution is obvious or my question is asked poorly.
So: I would like to use JWT to authenticate my users. I use axios, vue.js and of course JWT. I would like to access a secure route:
router.post('/secureroute', checkAuth, (req, res) => {
res.status(200).json({
message: 'all ok'
})
});
In order to do so, I use this check-auth.js:
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(" ")[1];
console.log(token);
const decoded = jwt.verify(token, process.env.SECRET_KEY);
next();
} catch (error) {
return res.status(401).json({
message: 'Auth failed'
})
}
next();
}
part of my Login.vue:
methods: {
login() {
if (!this.username) this.alertUsername = true;
if (!this.password) this.alertPassword = true;
axios
.post("/user/login", {
username: this.username,
password: this.password
})
.then(res => {
localStorage.setItem("usertoken", res.data.token);
if (res.data.token) {
console.log("Success");
router.push({ name: "start" });
} else {
this.alertWrong = true;
}
this.username = "";
this.password = "";
})
.catch(err => {
console.log(err);
});
this.emitMethod();
}
Using postman with an authorization header, everything seems to work fine. But after hours of searching the Internet and trying things out, I simply do not know how to make it work with the real website. I would like to pass the JWT as an authorization-header. I know that it is possible with axios, but I really don't know how I can do so in my example here.
You've got your login flow, and you are storing the usertoken in localStorage as the usertoken key. You also verified that your requests are processed correctly if the authorization header is set.
The easiest way to work with api requests is by abstracting axios a bit more, to automatically add the authorization token, and maybe pre-process the response you get back. For example, you may want to handle some errors globally instead of on a case-by-case basis, or want to transform the request into something that is always the same.
You first want to make some abstraction that calls axios.request. You can pass it a configuration object as described here. What's most important for you right now is the headers key-value pair, but you may want to expand this in the future.
export default request (config) {
const userToken = window.localStorage.getItem('usertoken');
const requestConfig = { ...config };
if (!requestConfig.headers) {
requestConfig.headers = {};
}
if (userToken) {
requestConfig.headers.authorization = `Bearer ${userToken}`;
}
return axios.request(requestConfig);
}
Now we can expand on that:
export default post (url, data = {}, config = {}) {
return request({
...config,
method: 'POST'
url,
data
});
}
When inspecting the request in your developer console you should now see that, if the user token is correctly set in localStorage, you have an extra header in your request that is sent to the server.

Categories