Spotify api returning invalid refresh token even though the refresh token is new - javascript

I'm trying to get new access token from spotify by sending the refresh token to spotify token endpoints but it's returning this {error: 'invalid_grant', error_description: 'Invalid refresh token'}
this is my code:
const basic = Buffer.from(
`${import.meta.env.VITE_CLIENT_ID}:${import.meta.env.VITE_CLIENT_SECRET}`
).toString("base64");
const params = new URLSearchParams();
params.append("grant_type", "refresh_token");
params.append("refresh_token", import.meta.env.VITE_REFRESH_TOKEN);
const response = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
headers: {
Authorization: `Basic ${basic}`,
"Content-Type": "application/x-www-form-urlencoded"
},
body: params.toString()
});
const result = await response.json();
return result;
It's suppose to return a new access token but it's returning error for some reasons i don't understand.
Note: I got the access token and refresh token from this website https://alecchen.dev/spotify-refresh-token/ after inputting my client id and client secret. If i use the access token directly to make a request to spotify api it works but i need to refresh it to get a new one but it's returning error

You needs to call this format in body of POST.
grant_type = refresh_token
refresh_token = <received refresh_token>
access_token= <received access_token>
The website https://alecchen.dev/spotify-refresh-token/ has a potential leak your credential.
I will shows getting refresh token in local and update refresh token.
Demo Code.
Save as get-token.js file.
const express = require("express")
const axios = require('axios')
const cors = require("cors");
const app = express()
app.use(cors())
CLIENT_ID = "<your client id>"
CLIENT_SECRET = "<your client secret>"
REDIRECT_URI = '<your redirect URI>' // my case is 'http://localhost:3000/callback'
SCOPE = [
"user-read-email",
"playlist-read-collaborative"
]
app.get("/login", (request, response) => {
const redirect_url = `https://accounts.spotify.com/authorize?response_type=code&client_id=${CLIENT_ID}&scope=${SCOPE}&state=123456&redirect_uri=${REDIRECT_URI}&prompt=consent`
response.redirect(redirect_url);
})
app.get("/callback", async (request, response) => {
const code = request.query["code"]
await axios.post(
url = 'https://accounts.spotify.com/api/token',
data = new URLSearchParams({
'grant_type': 'authorization_code',
'redirect_uri': REDIRECT_URI,
'code': code
}),
config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
params: {
'grant_type': 'client_credentials'
},
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
})
.then(resp1 => {
axios.post(
url = 'https://accounts.spotify.com/api/token',
data = new URLSearchParams({
'grant_type': 'refresh_token',
'refresh_token': resp1.data.refresh_token,
'access_token': resp1.data.access_token
}),
config = {
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
}
).then(resp2 => {
return response.send(JSON.stringify([resp1.data, resp2.data]));
})
});
})
// your port of REDIRECT_URI
app.listen(3000, () => {
console.log("Listening on :3000")
Install dependencies
npm install express axios cors
Run a local server and access it
node get-token.js
Open your browser and enter this address
http://localhost:3000/login
It will get code and both tokens then exchange the exchanged token.
It Will display both tokens and exchanged token in Browser.
Result
First red box is get access-token and refresh-token
Second red box is to grant the refresh-token

Related

Etsy API authetication - Cannot GET error

I'm very rusty in javascript and haven't touched it in a decade so I was following an Etsy tutorial on how to request an API access token.
I'm running a node.js node on my localhost and ngrok to get a proper URL for it (seems like localhost doesn't work for etsy). The authentication seems to work (I can log in in etsy with it), until the part in the tutorial where I need to send the API information onto the next page (to actually start pulling the etsy store data).
As soon as the etsy authentication page get redirected to the next page I alway get the error "Cannot GET views/index.hbs"
The page is in the views folder in the project folder.
I'm really not sure what the problem is, maybe the way the files are structured in the folder?
Thanks a lot for the help.
This is how the code looks like:
// Import the express library
const express = require('express')
const fetch = require("node-fetch");
const hbs = require("hbs");
// Create a new express application
const app = express();
app.set("view engine", "hbs");
app.set("views", `${process.cwd()}/views`);
// Send a JSON response to a default get request
app.get('/ping', async (req, res) => {
const requestOptions = {
'method': 'GET',
'headers': {
'x-api-key': 'xxxxxxxxxxxxxxx',
},
};
const response = await fetch(
'https://api.etsy.com/v3/application/openapi-ping',
requestOptions
);
if (response.ok) {
const data = await response.json();
res.send(data);
} else {
res.send("oops");
}
});
// This renders our `index.hbs` file.
app.get('/', async (req, res) => {
res.render("index");
});
/**
These variables contain your API Key, the state sent
in the initial authorization request, and the client verifier compliment
to the code_challenge sent with the initial authorization request
*/
const clientID = 'xxxxxxxxxxxxxxxx';
const clientVerifier = 'xxxxxxxxxxxxxxxxxxxxxx';
const redirectUri = 'https://xxxxxxx/views/index.hbs';
app.get("/oauth/redirect", async (req, res) => {
// The req.query object has the query params that Etsy authentication sends
// to this route. The authorization code is in the `code` param
const authCode = req.query.code;
const tokenUrl = 'https://api.etsy.com/v3/public/oauth/token';
const requestOptions = {
method: 'POST',
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: clientID,
redirect_uri: redirectUri,
code: authCode,
code_verifier: clientVerifier,
}),
headers: {
'Content-Type': 'application/json'
}
};
const response = await fetch(tokenUrl, requestOptions);
// Extract the access token from the response access_token data field
if (response.ok) {
const tokenData = await response.json();
res.send(tokenData);
} else {
res.send("oops");
}
});
app.get("/welcome", async (req, res) => {
// We passed the access token in via the querystring
const { access_token } = req.query;
// An Etsy access token includes your shop/user ID
// as a token prefix, so we can extract that too
const user_id = access_token.split('.')[0];
const requestOptions = {
headers: {
'x-api-key': clientID,
// Scoped endpoints require a bearer token
Authorization: `Bearer ${access_token}`,
}
};
const response = await fetch(
`https://api.etsy.com/v3/application/users/${user_id}`,
requestOptions
);
if (response.ok) {
const userData = await response.json();
// Load the template with the first name as a template variable.
res.render("welcome", {
first_name: userData.first_name
});
} else {
res.send("oops");
}
});
// Start the server on port 3003
const port = 3003;
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

How to implement the best OAuth authentication?

I'm using zendesk OAuth for authorization. I'm using the MERN stack and the current implementation works like this,
User clicks login and redirected to zendesk
once the user signs I get redirected back to /callback path
Where I sent another request to get an auth token
After I get the token I redirect the user to frontend as ?token=XXXX attached to the URL
Is this the correct way? How should I proceed with the token should I keep it in session storage? It's not a good idea to expose the token?
export const authCallback = (req: Request, res: Response): void => {
const body = {
grant_type: 'authorization_code',
code: req.query.code,
client_id: process.env.ZENDESK_CLIENT_ID,
client_secret: process.env.ZENDESK_SECRET,
}
axios
.post(`https://${process.env.SUBDOMAIN}.zendesk.com/oauth/tokens`, body, {
headers: {
'Content-Type': 'application/json',
}
})
.then((response) => {
const token = response.data.access_token
return res.redirect(`${process.env.ORIGIN}?token=${token}`)
})
.catch((err) => {
return res.status(400).send({ message: err.message })
})
}
Either use express-session and store the token on the server in req.session.token:
(response) => {
req.session.token = response.data.access_token;
req.session.save(function() {
res.redirect(`${process.env.ORIGIN}`)
});
}
Or send the token in a session cookie directly:
(response) => {
res.cookie("token", response.data.access_token, {
httpOnly: true,
secure: true,
sameSite: "None"
});
res.redirect(`${process.env.ORIGIN}`)
}

How to bypass Spotify API CORS redirection error

I'm trying to make a project that utilizes the Spotify web api, and am trying to implement a login/logout function using React and Express.
I'm trying to show a user's top songs only if the user is signed in - this means that there is a working access_token set in the cookies.
I'm a bit new when it comes to web development, so my understanding isn't super comprehensive, but this is my approach to doing this.
Check if there is an active access_token in cookies
If there is, proceed. If there isn't, send a post request to the backend /login route, which redirects the page to the spotify authorization, which eventually, places a token in the cookies.
When I do this, however, I get a CORS error saying that cross-origin redirection has been denied: origin null is not allowed by access-control-allow-origin. I ran the server in Firefox as well (instead of safari), and got a different error.(Reason: CORS header ‘Access-Control-Allow-Origin’ missing)
I saw this post (Spotify API CORS error with React front-end and Node back-end) where the user kind of has the same problem I do, and was a bit confused by the response. They were told to not redirect responses to API calls. Am I supposed to do everything on the client-side?
Here is some of my server-side and client code.
app.post('/refresh', (req, res) => {
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH,OPTIONS');
const refresh_token = req.body.headers.refresh_token;
const authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: {
'Authorization': 'Basic ' + (Buffer.from(client_id + ':' + client_secret).toString('base64')),
'Content-Type': 'application/x-www-form-urlencoded'
},
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
const access_token = body.access_token;
res.cookie('access_token', access_token);
res.send({
'access_token': access_token
});
}
});
});
app.get('/login', (req, res) => {
console.log('logging in 3');
const scope = [
'ugc-image-upload',
'user-read-playback-state',
'user-modify-playback-state',
'user-read-currently-playing',
'streaming',
'app-remote-control',
'user-read-email',
'user-read-private',
'playlist-read-collaborative',
'playlist-modify-public',
'playlist-read-private',
'playlist-modify-private',
'user-library-modify',
'user-library-read',
'user-top-read',
'user-read-playback-position',
'user-read-recently-played',
'user-follow-read',
'user-follow-modify'
];
const state = Math.random().toString(36).slice(2,18);
const auth_query_parameters = new URLSearchParams({
response_type: "code",
client_id: client_id,
scope: scope,
redirect_uri: 'http://localhost:3001/access',
state: state
});
res.redirect('https://accounts.spotify.com/authorize?' +
auth_query_parameters.toString());
});
useEffect(() => {
console.log(Cookies.get('refresh_token'))
console.log('getting refresh token');
const refresh_token = Cookies.get('refresh_token');
setAccessToken(Cookies.get('access_token'));
axios.post('http://localhost:3001/login')
.then(res => {
console.log(res)
})
}, [])

Browser not setting cookie after fetch request javascript

So the first time running this code, the browser set the cookie. But then when I cleared the cookie from the browser and tried to run the code again, the browser isn't setting the cookie any more. I tried multiple browsers and it doesn't work. The fetch request is successful and i can print the cookie value in the console, but the browser wont set the cookie. This is a very annoying problem. I tried using the credential: 'include' also but it didnt work. Getting a CORS error.
I'm pretty new to web development so my knowledge is not very deep.
This is the code on my front end
let reqObj = {
//check email to see if it is a valid format in the login.html user email input
//name: name.value,
email: email.value,
password: password.value
}
const response = await fetch(api_url + "api/user/login", {
method: 'POST',
body: JSON.stringify(reqObj),
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin'
});
const jwt = await response.text();
console.log(jwt)
this is the express server code
router.post('/login', async (req, res) => {
//validate data
const { error } = loginValidation(req.body)
if (error) return res.status(400).send(error.details[0].message);
//checking if email is in database
const user = await User.findOne({ email: req.body.email });
if (!user) return res.status(400).send('Email or password is incorrect')
//Password is correct
const validPass = await bcrypt.compare(req.body.password, user.password)
if (!validPass) return res.status(400).send('Invalid password')
// Create and assign a token
const token = jsonWebToken.sign({ _id: user.id }, process.env.TOKEN_SECRET);
//res.header('auth-token', token).send(token);
res.cookie('auth_token', token, {
maxAge: 3600,
httpOnly: true
}).send(token)
});
module.exports = router;
I believe that in order to use credentials: 'include' you have to add the Access-Control-Allow-Credentials header on your response.

Angular Global Functions

I am building an angular application that supports JWT authentication. I build my JWT server using Spring Boot. A user provides his/her credentials for authentication. After a successful authentication, a response containing the access token and authentication will be forwarded to the user.
There is a exp value in my JWT access token containing the token lifespan. The refresh token is persisted in a DB.
To get a new access token, you have to sent a request with this header
--->>>X-Auth: refreshToken<<<--- to get a new access token.
I implemented a function that will resend a request for an access token using the refresh token.
I had tried implementing the function to run the function globally but to no avail. I wish to find out if there is a way that I could run the function once globally in my angular app.
/***Login sample request***/
{
"password": "secret123",
"username": "nkengbeza"
}
/***Login sample response***/
{
"access_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJua2VuZ2JlemEiLCJhdXRoIjpbeyJhdXRob3JpdHkiOiJBRE1JTiJ9XSwidG9rZW5fdHlwZSI6ImFjY2Vzc190b2tlbiIsImlhdCI6MTU0ODY2OTE0MSwiZXhwIjoxNTQ4NjcyNzQxfQ.oaWOIaeMqLg35unM82bcNg88ga030m5J1k7E5EM2O0s",
"refresh_token": "a102c7a063c7f149bcb276eadf83c0c61659ff9b37c0a8ab7d29e92c08ac94e7aa99abc5e9a3c1c59da4e66f1ada0c6a02dca2f577a4aa3f81885c73f5a879d1"
}
/***Request for an access token using a valid refresh token***/
reLogin() {
(async () => {
const resp = await fetch(AppRoutes.RELOAD_AUTH, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Auth': this.getAuth().refreshToken
}
});
if (resp.status !== 200) {
this.doLogout();
}
const content = await resp.json();
const accessToken = content.access_token;
const tokenInfo = this.getDecodedAccessToken(accessToken); // decode token
const auth = new Auth();
auth.refreshToken = content.refresh_token;
auth.role = tokenInfo.auth[0];
const expireDate = tokenInfo.exp; // get token expiration dateTime
this.setAccessToken(accessToken);
this.setAuth(auth);
})();
}

Categories