Illegal redirect_uri trying to authenticate with spotify api - spotify

var request = require('request'); // "Request" library
var client_id = 'id'; // Your client id
var client_secret = 'secret'; // Your secret
var redirect_uri = 'http://localhost:8888/callback/'; // Your redirect u
I'm trying to authenticate with the spotify api but I'm getting a 400 error saying Illegal redirect_uri. My redirect URL on spotify dashboard is IDENTICAL so I'm not sure sure why this error is showing.

Related

Spotify API Authorization redirects too many times

I'm trying to use the Spotify API and following their instructions on authorization found here: https://github.com/spotify/web-api-auth-examples/blob/master/authorization_code/app.js.
Their version of the Authorization code directly uses routes in the server code, but I wanted to separate the Authorization into its own route. Here is my version of the code:
const authRouter = require("express").Router();
const config = require("../utils/config");
const request = require("request");
const querystring = require("querystring");
// Spotify client configurations
const client_id = config.CLIENT_ID;
const client_secret = config.CLIENT_SECRET;
const redirect_uri = config.REDIRECT_URI;
const stateKey = "spotify_auth_state";
//...
// #route GET /
// #desc Prompt User to Login into Spotify
authRouter.get("/", async (req, res) => {
try {
var state = generateRandomString(16);
res.cookie(stateKey, state);
// Request for user full name, profile image, and email address.
var scope = "user-read-private user-read-email";
// 1. Get the user's authorization to access data.
res.redirect(
"https://accounts.spotify.com/authorize?" +
querystring.stringify({
response_type: "code",
client_id: client_id,
scope: scope,
redirect_uri: redirect_uri,
state: state,
})
);
} catch {
console.log("error.");
}
});
// #route GET /callback
// #desc Spotify callback to request access and refresh tokens
authRouter.get("/callback", async (req, res) => {
try {
var code = req.query.code || null; // The authorization code returned by the first call.
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;
// Check the state parameter
if (state === null || state !== storedState) {
res.redirect(
"/#" +
querystring.stringify({
error: "state_mismatch",
})
);
} else {
res.clearCookie(stateKey);
const authOptions = getAuthOptions(
code,
redirect_uri,
client_id,
client_secret
);
// 2. Request an access token and refresh token
request.post(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
// Authorize successful. Access and Refresh Tokens granted.
var access_token = body.access_token,
refresh_token = body.refresh_token;
// Send the tokens to the client so the client can use them in requests to the Spotify API.
res.redirect(
"/#" +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token,
})
);
} else {
res.redirect(
"/#" +
querystring.stringify({
error: "invalid_token",
})
);
}
});
}
} catch {
console.log("error.");
}
});
module.exports = authRouter;
And in my app.js:
const express = require("express");
const authRouter = require("./controllers/auth");
const cors = require("cors");
var cookieParser = require("cookie-parser");
// initialize app with Express to create client to communicate with Spotify
const app = express();
app.use(cors());
app.use(cookieParser());
app.use("/", authRouter);
module.exports = app;
Now when I start my server, my browser returns: "accounts.spotify.com redirected you too many times.". When I tried starting my server in incognito mode, the Spotify login prompt appears. After I enter my credentials, it returns: "accounts.spotify.com redirected you too many times."
I've tried clearing my cookies and caches but that does not work.
In addition, I've confirmed my redirect URI for my server is the same as my redirect URI in my Spotify application's settings.
What can be the reasons the auth seems to be stuck in an infinite loop?
What's causing the infinite loop is where the code sends the access and refresh tokens back to the client:
// Send the tokens to the client so the client can use them in requests to the Spotify API.
res.redirect(
"/#" +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token,
})
);
Since I have defined the following route:
authRouter.get("/", async (req, res) => {
The access and refresh tokens are redirected to the login page, which will then lead to the callback which redirects to the login again, creating an infinite loop.
How I solved this was to redirect the access and refresh tokens to a different component, not just to "/#" + query.string... as coded in Spotify's example code.
Spotify's example code does not lead to an infinite loop since they defined a /login route for the login page, but I opted my website's root to be the login page since in my case, authenticating should be the first step.

jwt accessing private page after login works on postman, but not on ejs view

I have the backend of a little register/login project on node, which works fine on postman, I'm doing the frontend using just ejs views, the registration works fine and the login alone too, but if I go to the private page, that works with the jwt token, it doesn't find the token I supposedly got when logged in, console says it's undefined.
This is the verification code.
const jwt = require('jsonwebtoken');
module.exports = function (req,res,next){
const token = req.header('auth-token');
console.log(token);
if(!token) return res.status(401).send('access denied');
try {
const verified = jwt.verify(token,process.env.TOKEN_SECRET);
req.user = verified;
//see private content
next();
} catch (err) {
res.status(401).send('invalid token');
}
}
this is the backend of the posts page
const router = require('express').Router();
const verify = require('./verifyToken');
//the verify marks this content as private
router.get('/',verify,(req,res)=>{
res.render('posts.ejs');
});
module.exports = router;
On postman I fill the token name on the headers, but how can I do something like this on the actual thing?
I searched a bit on this and we cannot pass headers to a url.
You can check out this question
Adding http request header to a a href link
When doing an ajax request however we can do attach custom headers and all that. We have full control over the request. I will advise you to use sessions in place of jsonwebtokens. Json Web Tokens are mostly used when using a Front End Framework React, Angular etc because we have to make ajax requests. We than save the token in localStorage and send the token in every subsequent request in the header.

How to SSO in another domain using express server/react client and redirect to another domain?

Problem is to create a web app that has a form that sends it to my express server, generates a token from external endpoint post with form value, uses token from external domain to make a post to external endpoint to then get authenticated into external domain.
I am able to generate token on my express server, make the second post with token to external endpoint and able to login and see the response on express server.
However, when I try to redirect my react client to the external domain, it shows a timeout message.
I've tried making the 2nd post from client with Axios and Fetch and then redirect to external domain, but it gives me CORS errors, until I turn on CORS chrome plugin, then it still gives me the same timeout message.
I've tried adding different headers to my post call to allow redirects, but no success.
const router = require('express').Router();
const xml2js = require('xml2js');
const parseString = require('xml2js').parseString;
const request2 = require('request');
// const axios = require('axios')
var setCookie = require('set-cookie-parser')
var Cookies = require('js-cookie')
require('dotenv').config();
router.post('/sso', (req, response, next)=>{
// SETTING UP XML BODY FOR TOKEN GENERATOR
// USING TEMPLATE TO BUILD XML BODY FOR TOKEN GENERATOR
// SETTING UP POST TO TOKEN GENERATOR WITH XML TEMPLATE
// DECLARING TOKEN TO PASS TO SSO POST
// PROMISE FOR RESPONSE POST TO TOKEN GENERATOR
return new Promise((resolve)=>{
// ERROR CATCH BLOCK FOR POST TO TOKEN GENERATOR
try {
// POST TO TOKEN GENERATOR USING XML TEMPLATE
request2(TokenGenerator,
(err, res, body)=>{
// PARSE TOKEN GENERATOR BODY RESPONSE
// CONVERTING TO STRING SOAP BODY
// PARSING STRING INTO JSON TO TARGET TOKEN
// DECLARING TOKEN RESPONSE IN RES WITH TOKEN VALUE FROM POST TO TOKEN GENERATOR
// ASSIGNING IT TO GLOBAL VARIABLE
})
// TRYING POST FROM CLIENT HAS BEEN COMMENTED OUT
// // response.send(token)
// // next()
// SETTING UP POST TO PARTICIPANT SSO WITH TOKEN VALUE
const secondPostToSSO = {
method: 'POST',
url: 'externaldomain.com/sso.aspx',
followAllRedirects: true,
jar: true,
headers: {
'Content-Type': 'text/html'
},
form: {
'TOKEN': token
}
}
// POST TO PARTICIPANT SSO WITH TOKEN
request2.post(secondPostToSSO,(err, response2, body2)=>{
console.log(response2.request)
var cookies = setCookie.parse(response2, {
decodeValues: true,
map: true
})
console.log(cookies)
next()
})
})
} catch (e) {
console.error(`[FATAL ERROR] - KAPUT - ${e}`)
return res.redirect('/')
}
})
})
module.exports = router
I expect the output of the server post to then redirect the client to the externaldomain.com where I'm getting the token, and making post with token to authenticate client. The outcome should be that the client has been logged from my web app to the external domain.
I was able to solve the CORS issue and complete the application a few weeks ago and I wanted to share my answer.
The way I solved the CORS issue was to remove React and use simple HTML client to the server side on the same port. I use the npm package cors (https://www.npmjs.com/package/cors). I added one line to the server.js file like this: app.use(cors());.
After that, I updated my SSO route to pass the token with response.send(token) instead of using next().
Once the token was received by the browser, remember, I have server and client on same port, client would trigger a hidden form POST once the token was received from the server.
VoilĂ , that solved it.
The API route:
const path = require('path');
const router = require('express').Router();
const app = require('express')
const xml2js = require('xml2js');
const parseString = require('xml2js').parseString;
const request2 = require('request');
const cors = require('cors')
require('dotenv').config();
// API routes
module.exports.routes = function (app) {
// POST API
app.post("/api/sso", (request, response, next)=>{
// SETTING UP XML BODY FOR TOKEN GENERATOR
const Template = {
// YOUR TEMPLATE
}
// USING TEMPLATE TO BUILD XML BODY FOR TOKEN GENERATOR
const builder = new xml2js.Builder()
const xml = builder.buildObject(Template)
// SETTING THE RIGHT ENVIRONMENT LINK FOR TOKEN GENERATOR BASED ON USER INPUT WITH IF STATEMENT
// SETTING UP POST TO TOKEN GENERATOR WITH XML TEMPLATE
const TokenGenerator = {
url: tokenGeneratorUrl,
method: 'POST',
headers: {
'Content-Type': 'text/xml'
},
body: xml
}
// DECLARING TOKEN TO PASS TO SSO POST
let TOKEN = ''
// PROMISE FOR RESPONSE POST TO TOKEN GENERATOR
return new Promise((resolve) => {
// ERROR CATCH BLOCK FOR POST TO TOKEN GENERATOR
try {
// POST TO TOKEN GENERATOR USING XML TEMPLATE
request2(TokenGenerator,
(err, res, body) => {
// PARSE TOKEN GENERATOR BODY RESPONSE
parseString(body,
(err, result) => {
// CONVERTING TO STRING SOAP BODY
const strBody = JSON.stringify(result['soap:Envelope']['soap:Body'])
// PARSING STRING INTO JSON TO TARGET TOKEN
// DECLARING TOKEN RESPONSE IN RES WITH TOKEN VALUE FROM POST TO TOKEN GENERATOR
// ASSIGNING IT TO GLOBAL VARIABLE
TOKEN = res.TokenResponse
})
// SENDING TOKEN TO CLIENT TO POST TO SSO
response.send(TOKEN)
})
} catch (e) {
console.error(`[FATAL ERROR] - KAPUT - ${e}`)
return res.redirect('/')
}
})
});
};
The hidden form on the client:
<form style="display: none;" novalidate autocomplete="off" name="hiddenForm" id="hiddenForm" action="https://api.endpoint.com/route" method="POST" class="hiddenForm">
<input type="hidden" id="TOKEN" name="TOKEN" value="">
</form>

Dropbox api V2, get access token in query param instead of url hash (#) (Nodejs)

I'm using the official Dropbox API (V2) on my Nodejs app.
It sounds like a dumb question but I really can't find out how to get the given access token from the callback url. Actually, it is supposed to be in the hash (#) part of the url (according to their documentation and javascript client-side exemple), which is not visible by the server side...
I can't find any exemple for authentication from a nodejs app, using only the basic api.
Here is my authentication code:
My express app:
//Entry point, DC is a DropboxConnector object
app.get('/connect/Dropbox', function(req, res) {
console.log('/connect/Dropbox called');
res.redirect(DC.getConnexionURL());
});
// Callback from the authentication
app.get('/authDropbox', function(req, res) {
console.log("/authDropbox called");
console.log(url.format(req.protocol + '://' + req.get('host') + req.originalUrl));
// The above log is: 'http://localhost:8080/authDropbox'
// Here is the problem, the access token is unreachable by express
DC.getToken(req.query.code, res);
connectorList.push(DC);
});
DropboxConnector.js, my dropbox api wrapper:
var REDIRECT_URI = 'http://localhost:8080/authDropbox';
//The authentication url given by the dropbox api
getConnexionURL() {
dbx = new Dropbox({ clientId: CLIENT_ID});
var authUrl = dbx.getAuthenticationUrl(REDIRECT_URI);
console.log("AuthURL: " + authUrl);
return authUrl;
}
// #param code is supposed to be the access token...
getToken(code, res) {
if (!!code) {
dbx = new Dropbox({ accessToken: code });
console.log("Authenticated!");
res.redirect(CALLBACK_URL);
} else {
console.log("No code here");
}
}
Thanks for help !
That's correct, the contents of the fragment a.k.a. hash are not visible to the server, only the client (browser). The OAuth 2 "token" flow sends the access token on the fragment, and is mainly meant for client-side apps, e.g., JavaScript in the browser. The OAuth 2 "code" flow instead sends an authorization code as a URL parameter, for server-side apps.
If you're interested, you can find more information on the two different flows in the Dropbox /oauth2/authorize documentation.
The Dropbox API v2 JavaScript SDK unfortunately currently only supports the "token" flow, but we're tracking this as a feature request for support for the "code" flow.
If you do not want to call HTTP directly, you can use my tiny dropbox-v2-api wrapper package:
const dropboxV2Api = require(dropbox-v2-api');
const dropbox = dropboxV2Api.authenticate({
client_id: 'APP_KEY',
client_secret: 'APP_SECRET',
redirect_uri: 'REDIRECT_URI'
});
//generate and visit authorization sevice
const authUrl = dropbox.generateAuthUrl();
//after redirection, you should receive code
dropbox.getToken(code, (err, response) => {
//you are authorized now!
});
Full example (see here):
const dropboxV2Api = require(dropbox-v2-api');
const Hapi = require('hapi');
const fs = require('fs');
const path = require('path');
const Opn = require('opn');
const credentials = JSON.parse(fs.readFileSync(path.join(__dirname, 'credentials.json')));
//set auth credentials
const dropbox = dropboxV2Api.authenticate({
client_id: credentials.APP_KEY,
client_secret: credentials.APP_SECRET,
redirect_uri: 'http://localhost:5000/oauth'
});
//prepare server & oauth2 response callback
const server = new Hapi.Server();
server.connection({ port: 5000 });
server.route({
method: 'GET',
path: '/oauth',
handler: function (request, reply) {
var params = request.query;
dropbox.getToken(params.code, function(err, response){
console.log('user\'s access_token: ',response.access_token);
//call api
dropbox({
resource: 'users/get_current_account'
}, function(err, response){
reply({response: response});
});
});
}
});
server.start(function(){
//open authorization url
Opn(dropbox.generateAuthUrl());
});

Coinbase Connect (OAuth2) "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."

Dear stackoverflowser,
I'm stying to build an Electron app with Coinbase intergration.
First I'm going to make the server (NodeJS) work with OAuth2.
Every thing works great, but when I want to change the code into an access token with the instructed post request it gives me the following error:
{
error: "invalid_request",
error_description: "The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed."
}
I already added https://localhost:3000/auth/coinbase/callback and https://localhost:3000/profile to the valid API URI's.
I wasn't successful in figuring it out after several hours.
My server is this:
var express = require('express');
var app = express();
var fs = require('fs')
var https = require('https');
var coinbase = require('coinbase')
var request = require('request');
var options = {
key: fs.readFileSync('./ssl/coinbase.dev.key'),
cert: fs.readFileSync('./ssl/coinbase.dev.crt'),
};
var client_id = 'gues it'
var client_secret = 'gues it'
app.use(express.static('static'));
app.get('/login/coinbase', function(req, res) {
res.redirect('https://www.coinbase.com/oauth/authorize?response_type=code&redirect_uri=https://localhost:3000/auth/coinbase/callback&client_id=' + client_id + '&scope=wallet:user:read,wallet:accounts:read')
})
app.get('/auth/coinbase/callback', function(req, res) {
var data = {
client_id: client_id,
client_secret: client_secret,
grant_type: 'authorization_code',
code: req.query.code,
redirect_uri: 'https://localhost:3000/profile'
}
request.post(
'https://api.coinbase.com/oauth/token', data, function (error, response, body) {
console.log(body)
res.send(body)
}
);
})
app.get('/', function(req, res) {
res.send('home')
})
app.get('/profile', function(req, res) {
res.send('profile')
})
var server = https.createServer(options, app);
server.listen(3000)
Thanks in advance,
Theo
[EDIT]
I contacted the Coinbase developers and they were surprised that there was no NodeJS example on OAuth with Coinbase, so they added it to their roadmap.
This is most likely caused by one of the following:
You do not have 'http://127.0.0.1:3000/profile' listed as a valid redirect API in your application settings.
You are reusing an authorization code that has already been exchanged for a token.
This section of this page:
OAuth2 Redirect URI
For added security, all redirect_uris must use SSL (i.e. begin with
https://). URIs without SSL can only be used for development and
testing and will not be supported in production.
Contact api#coinbase.com to get that sorted out.

Resources