Spotify API Authorization redirects too many times - node.js

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.

Related

Google API CORS error on localhost, "No 'Access-Control-Allow-Origin' header is present on the requested resource."

I'm building a small MERN stack application which will contain calendar. The intention with the calendar is to sync it with multiple employees' Google Calendars for scheduling purposes.
I've started testing the Google Calendar API, and their test code here works just fine when I run it directly with Node: https://developers.google.com/calendar/api/quickstart/nodejs
However, now I'm trying to configure a button which will trigger a request from React to the Express backend and hit a route (and use the subsequent callback route) in order to authorize the employee's (the Operator) work Google account and capture the necessary credentials to store in the database. This way, the app can save the auth and refresh tokens needed for each Operator to keep the Google Calendars continuously synced with the application's calendar. This doesn't seem to work, while I'm in development at least, for some reason I keep getting this error:
Access to fetch at '<the generated oauth2 url>' (redirected from 'http://localhost:3500/api/auth/<the email>') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: 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've thought of instead doing this all in the frontend and sending it to my database from there, but that seems insecure.
I set up cors and I have 'http://localhost:3000' listed as an allowed origin, it works for everything else but this.
I've also tried using a proxy, but I can't seem to get my frontend to use it. I've tried the http-proxy-middleware package, and I've tried simply adding "proxy": "http://localhost:3500" to my package.json. None of it takes, console logging the request origins on my server show they are still coming from localhost:3000.
I've been toiling away at this for a while now, and I can't seem to get past it.
This is the query builder from the apiSlice (this works as the route is definitely called. I think the issue might actually be what I'm getting back from the auth redirect?):
getOperatorAuth: builder.query({
query: (operatorEmail) => ({
url:`/api/auth/${operatorEmail}`,
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
}),
Here are the routes:
const express = require('express')
const router = express.Router()
const operatorsController = require('../controllers/operatorsController')
router.route('/auth/:operatorEmail')
.get(operatorsController.getGoogleAuth)
router.route('/callback')
.get(operatorsController.getGoogleAuthCallback)
module.exports = router
And here are the functions they call:
const getGoogleAuth = async (req, res) => {
console.log(req.headers.origin)
console.log("requesting auth route")
const operatorEmail = req.params.operatorEmail;
const oAuth2Client = new OAuth2Client(clientId, clientSecret, redirectUri);
const authorizeUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: ['https://www.googleapis.com/auth/calendar'],
state: JSON.stringify({ operatorEmail }),
});
res.redirect(authorizeUrl);
}
const getGoogleAuthCallback =async (req, res) => {
console.log("google callback")
const code = req.query.code;
const state = JSON.parse(decodeURIComponent(req.query.state));
const operatorEmail = state.operatorEmail;
const data = querystring.stringify({
code: code,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
grant_type: 'authorization_code'
});
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
try {
const response = await axios.post(
'https://oauth2.googleapis.com/token',
data,
{ headers: headers }
);
const accessToken = response.data.access_token;
const refreshToken = response.data.refresh_token;
// Store the access token and refresh token in MongoDB
const operatorAuth = new OperatorAuth({
email: operatorEmail,
accessToken: accessToken,
refreshToken: refreshToken
});
await operatorAuth.save();
res.send('Access token and refresh token received and stored');
} catch (error) {
res.send(error.message);
}
}
And CORS setup:
Server.js
const cookieParser = require('cookie-parser')
const cors = require('cors')
const corsOptions = require('./config/corsOptions')
const connectDB = require('./config/dbConn')
const mongoose = require('mongoose')
console.log(process.env.NODE_ENV)
connectDB()
app.use(cors(corsOptions))
app.use(express.json())
app.use(cookieParser())
app.use('/', express.static(path.join(__dirname, 'public')))
app.use('/api', require('./routes/apiRoutes'))
corsOptions.js
const allowedOrigins = require('./allowedOrigins')
const corsOptions = {
origin: (origin, callback) => {
if (allowedOrigins.indexOf(origin) !== -1 || !origin) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true,
optionsSuccessStatus: 200
}
module.exports = corsOptions
and allowedOrigins.js:
const allowedOrigins = [
'http://localhost:3000'
]
module.exports = allowedOrigins

Authentication using Node.js OauthClient "auth-code" flow

I'm building a SaaS application which require read-access to a user's google calendar. After the user gives consent to access their calendar during the first sign-in, I want the application to be able to authorize itself to access any of the user's calendars at any time without having to prompt the user for authorization again.
Currently I'm trying to create an authentication flow following the '#react-oauth/google' node library (specifically the "authorization code flow" here: https://react-oauth.vercel.app/). In my frontend, I get a code from the user, which is sent and successfully received by my backend. The backend is then supposed to use the code to get an access token and a refresh token for that user, but the request to exchange the code for the access token (oAuth2Client.getToken(req.body.code);) is failing with error 401 (unauthorized client.)
My ultimate goal is to store the access token and refresh token in a database somewhere so that I can access that user's calendar later at any time.
If I treat the backend as an Oath Client on google cloud and pass in the credentials for that, I get error 401 - unauthorized client, but I've given it access to the calendar api on google console, as you can see in the image:
How can I resolve the issue that I'm facing?
I started reading about service accounts that can do this for you but I'm unsure how to proceed. I saw that they can do domain wide delegation but my users will be signing in from their personal gmail accounts so that option is not applicable for me.
Frontend Code:
import { useGoogleLogin } from '#react-oauth/google';
import axios from 'axios';
export const LoginModal = () => {
const googleLogin = useGoogleLogin({
flow: "auth-code",
onSuccess: async codeResponse => {
console.log(codeResponse);
const tokens = await axios.post("http://localhost:3001/auth/google/", {
code: codeResponse.code
});
console.log(tokens);
}
})
return (<>
...some html code
<button onClick={() => { googleLogin() }}>
..some more html code
</>)
}
Backend Code:
require('dotenv').config();
const express = require('express');
const {
OAuth2Client,
} = require('google-auth-library');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
const CLIENT_ID = "xxx";
const CLIENT_SECRET = "xxx";
// initialize oathclient
const oAuth2Client = new OAuth2Client(
CLIENT_ID,
CLIENT_SECRET,
'postmessage',
);
// get token from code given from frontend
app.post('/auth/google', async (req, res) => {
console.log(req.body.code)
const { tokens } = await oAuth2Client.getToken(req.body.code); // exchange code for tokens
res.json(tokens);
});
app.post('/auth/google/refresh-token', async (req, res) => {
const user = new UserRefreshClient(
CLIENT_ID,
CLIENT_SECRET,
req.body.refreshToken,
);
const { credentials } = await user.refreshAccessToken(); // obtain new tokens
res.json(credentials);
})
app.listen(3001, () => {
console.log(`server is running`)
});
I figured it out. Basically, I was putting in the wrong clientid/client secret in my google console because I thought the frontend and backend needed different oauth client IDs. I used the same oauth client secret/id for frontend and backend and made sure to follow the answers here:
https://github.com/MomenSherif/react-oauth/issues/12
MAKE SURE TO PUT "postmessage" AS YOUR REDIRECT_URI! It will not work without that.
Working code:
frontend is the same
backend:
require('dotenv').config();
const express = require('express');
const {
OAuth2Client,
} = require('google-auth-library');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
const CLIENT_ID = XXX
const CLIENT_SECRET = XXX
// initialize oathclient
const oAuth2Client = new OAuth2Client(
CLIENT_ID,
CLIENT_SECRET,
'postmessage',
);
// get token from code given from frontend
app.post('/auth/google', async (req, res) => {
console.log("got request!")
console.log(req.body.code)
const { tokens } = await oAuth2Client.getToken(req.body.code); // exchange code for token
res.json(tokens);
});
app.listen(3001, () => {
console.log(`server is running`)
});

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>

Express & Firebase - Failing to set header before redirect

I am trying to make Firebase authentication work on the server.
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({origin: true});
//const expressSanitizer = require('express-sanitizer');
const app = express();
// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const validateFirebaseIdToken = (req, res, next) => {
console.log('Check if request is authorized with Firebase ID token');
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
!(req.cookies && req.cookies.__session)) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
'Make sure you authorize your request by providing the following HTTP header:',
'Authorization: Bearer <Firebase ID Token>',
'or by passing a "__session" cookie.');
res.redirect("/login");
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
console.log('Found "Authorization" header');
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else if(req.cookies) {
console.log('Found "__session" cookie');
// Read the ID Token from cookie.
idToken = req.cookies.__session;
} else {
// No cookie
res.redirect("/login");
return;
}
admin.auth().verifyIdToken(idToken).then((decodedIdToken) => {
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
return next();
}).catch((error) => {
console.error('Error while verifying Firebase ID token:', error);
res.redirect("/login");
});
};
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("/public"));
app.use(cors);
app.use(cookieParser);
//app.use(expressSanitizer());
//app.use(validateFirebaseIdToken);=
app.set("view engine", "ejs");
// This HTTPS endpoint can only be accessed by your Firebase Users.
// Requests need to be authorized by providing an `Authorization` HTTP header
// with value `Bearer <Firebase ID Token>`.
exports.app = functions.https.onRequest(app);
app.post("/login", (request, response) => {
var idToken = request.body.token;
console.log("REQUEST BODY = " + idToken);
response.header("Authorization" , "Bearer " + idToken);
return response.redirect("dashboard");
});
app.get("/dashboard", validateFirebaseIdToken, (request, response) => {
response.redirect("/dashboard/new");
});
In the /login POST route, I am receiving the idToken as expected (and showed in the logs). It seems though, that the response is unable to preserve/maintain the header property Authentication: Bearer <Firebase ID token> set beforehand.
In fact, I sent a GET request in Postman to /dashboard by getting the idToken printed by the logs and setting it in the header of the request like Authorization: Bearer <idToken> and it worked perfectly.
Here it says that redirects are in fact new HTTPS requests and therefore don't preserve the header set in the response. What should I do in this case?
You have to send the Authorization header with every request. HTTPS functions are stateless. They don't remember anything from a prior request. So, you shouldn't depend on redirect behavior to retain state. Instead, the client needs to figure out where to go next and make the next request itself.

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());
});

Resources