6

I'm new to Oauth and server-side stuff, so please be patient with me.

I have a web application that authenticates users with dropbox-js. Everything is pretty straightforward. The application uses dropbox-js' client.authenticate function, and if the user is authenticated, the application gets automatically redirected to the initial page, where it executes the authenticate callback. From that moment on, I know I'm happily authenticated with Dropbox, and I can do stuff with the app's Dropbox directory.

I got a public node.js server that currently does nothing. What I would like to do is:

  • As soon as the client is authenticated, call my server and tell it that the user is authenticated
  • If the user doesn't exist on the server database, create an entry for it him/her the user database (I don't need detailed instructions to do this). If it exists, send back the user's associated data.

How can I do that in a secure way? I mean, how can the server tell that the user is a valid Dropbox user? Should the server authenticate to Dropbox on its side with the user credentials? What is the workflow in these cases?

janesconference
  • 6,333
  • 8
  • 55
  • 73

1 Answers1

10

At the end of the authentication process, you have an access token, which is what's used to make calls to the API. If both the client and the server need to make calls to the API, then both will need to have the access token.

If you're doing the authentication client-side today, you could pull the access token out somehow (not sure if/how it's exposed from the library, but it's in there somewhere and also storaged in local storage) and pass it to the server. The server can then use it to call /account/info and get the Dropbox user ID of the authenticated user.

An alternative is to do it the other way around. Authenticate the user with the "code flow" (rather than "token flow") and get the access token on the server in the first place. Then you could pass it down to the client and pass it as an option in the Dropbox.Client constructor. I think that dropbox-js supports this itself, but it's also not hard to do yourself. Here's some raw Express code that logs in a user and displays his or her name:

var crypto = require('crypto'),
    express = require('express'),
    request = require('request'),
    url = require('url');

var app = express();
app.use(express.cookieParser());

// insert your app key and secret here
var appkey = '<your app key>';
var appsecret = '<your app secret>';

function generateCSRFToken() {
    return crypto.randomBytes(18).toString('base64')
        .replace(/\//g, '-').replace(/\+/g, '_');
}
function generateRedirectURI(req) {
    return url.format({
            protocol: req.protocol,
            host: req.headers.host,
            pathname: app.path() + '/callback'
    });
}

app.get('/', function (req, res) {
    var csrfToken = generateCSRFToken();
    res.cookie('csrf', csrfToken);
    res.redirect(url.format({
        protocol: 'https',
        hostname: 'www.dropbox.com',
        pathname: '1/oauth2/authorize',
        query: {
            client_id: appkey,
            response_type: 'code',
            state: csrfToken,
            redirect_uri: generateRedirectURI(req)
        }
    }));
});

app.get('/callback', function (req, res) {
    if (req.query.error) {
        return res.send('ERROR ' + req.query.error + ': ' + req.query.error_description);
    }

    // check CSRF token
    if (req.query.state !== req.cookies.csrf) {
        return res.status(401).send(
            'CSRF token mismatch, possible cross-site request forgery attempt.'
        );
    } else {
        // exchange access code for bearer token
        request.post('https://api.dropbox.com/1/oauth2/token', {
            form: {
                code: req.query.code,
                grant_type: 'authorization_code',
                redirect_uri: generateRedirectURI(req)
            },
            auth: {
                user: appkey,
                pass: appsecret
            }
        }, function (error, response, body) {
            var data = JSON.parse(body);

            if (data.error) {
                return res.send('ERROR: ' + data.error);
            }

            // extract bearer token
            var token = data.access_token;

            // use the bearer token to make API calls
            request.get('https://api.dropbox.com/1/account/info', {
                headers: { Authorization: 'Bearer ' + token }
            }, function (error, response, body) {
                res.send('Logged in successfully as ' + JSON.parse(body).display_name + '.');
            });

            // write a file
            // request.put('https://api-content.dropbox.com/1/files_put/auto/hello.txt', {
            //  body: 'Hello, World!',
            //  headers: { Authorization: 'Bearer ' + token }
            // });
        });
    }
});

app.listen(8000);
user94559
  • 59,196
  • 6
  • 103
  • 103
  • I'm going for the first option (I already have everything in place). Please note that the server does not need to use the Dropbox API, I only want to authenticate (aka I want to know, on the server, that I have a valid user with valid credentials). So the workflow 1) is send the token to the server -> 2) get the username on the server corresponding to the token -> 3) update the table entry corresponding to the username? If yes, does this workflow pose security holes? – janesconference Aug 01 '13 at 15:51
  • You should use the user ID (`uid`) rather than the name, since names may collide. You get that back from `/account/info` too. – user94559 Aug 01 '13 at 16:04
  • Regarding security, you have to send the access token to the server, and do an /account/info call there. You can't pass the uid from the client, and have the server trust it. You probably know this already, I'm adding it in just to make sure. – pwnall Aug 01 '13 at 22:13