Omar Al-Ithawi
Omar Al-Ithawi

Reputation: 5160

Search fourquare place using JavaScript WITHOUT exposing the client secret

I was reading Foursquare API and trying to find how to use it for places search (as alternative to Google Places). However I was surprised that it requires the client secret key to be provided always!.

I'm using it in the browser, and the only way to get a response is to provide both client secret and client id in the request. It's "Client Secret" there should be another way of doing this, without using the client secret. Google API checks for the referring url and client id. Do Foursquare supports something like this?

I know that I can make the request in my server to foursquare and call it from my JavaScript client code. But that would be very ugly (IMO) since user have to wait for doubled response time user -> my-server -> foursquare-api instead of just user -> foursquare-api.

A page in my Web Application that would use it: Where can I a great place in the city to see Christmas lights in Boston, MA?, try to share a tip in order to find out (you don't have to really post it!).

Upvotes: 1

Views: 734

Answers (3)

Gwozdziu
Gwozdziu

Reputation: 1

A proxy approach really helped me. My app is running on embedded jetty and I used a ProxyServelt to approach this problem:

    import java.net.URI;

    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.ws.rs.core.UriBuilder;

    import org.eclipse.jetty.client.HttpClient;
    import org.eclipse.jetty.client.api.Request;
    import org.eclipse.jetty.proxy.ProxyServlet;
    import org.eclipse.jetty.util.ssl.SslContextFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class FoursquareProxyServlet extends ProxyServlet {
        public  static final String FOURSQUARE_API_PREFIX = "foursquare";

    private static final long serialVersionUID = 1L;
    private static final Logger LOG = LoggerFactory.getLogger(FoursquareProxyServlet.class);
    private static final String FOURSQUARE_API_VERSION = "20141026";

    private String apiURL;
    private String clientId;
    private String clientSecret;

    public void init() throws ServletException {
        super.init();

        ServletConfig config = getServletConfig();
        apiURL       = config.getInitParameter("foursquare.apiUrl");
        clientId     = config.getInitParameter("foursquare.clientId");
        clientSecret = config.getInitParameter("foursquare.clientSecret");
    }

    @Override
    protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request) {
        proxyRequest.getHeaders().remove("Host");
    }

    @Override
    protected URI rewriteURI(HttpServletRequest request) {
        URI uri = UriBuilder.fromUri(this.apiURL)
            .path(request.getRequestURI().replaceAll("/foursquare", ""))
            .replaceQuery(request.getQueryString().trim())
            .queryParam("client_id", this.clientId)
            .queryParam("client_secret", this.clientSecret)
            .queryParam("v", FOURSQUARE_API_VERSION)
            .build();

        return uri;
    }

    protected HttpClient newHttpClient() {
        SslContextFactory sslContextFactory = new SslContextFactory();
        HttpClient httpClient = new HttpClient(sslContextFactory);
        return httpClient;
    }
}

Then you just need to plug it into your server:

protected ServletContextHandler createFoursquareProxy() {
    ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
    context.setContextPath("/"+FoursquareProxyServlet.FOURSQUARE_API_PREFIX+"/*");

    ServletHolder foursquareProxy = new ServletHolder("foursquare", new FoursquareProxyServlet());
    foursquareProxy.setInitParameter("foursquare.apiUrl", this.foursquareApiUrl);
    foursquareProxy.setInitParameter("foursquare.clientId", this.foursquareClientId);
    foursquareProxy.setInitParameter("foursquare.clientSecret", this.foursquareClientSecret);

    context.addServlet(foursquareProxy, "/*");
    return context;
}

Your request in this case would look like this:

GET http://{host}:{port}/foursquare/venues/search?&ll=40.7,-74%20&query=sushi

I'm sure you will be able to figure out something similar with your stack.

Upvotes: 0

Tom van der Woerdt
Tom van der Woerdt

Reputation: 29985

This is a trick I learned from a fellow developer.

It's not a proxy, yet similar. It will still make the requests from your IP address (not the server) yet the signing happens on the server. It doesn't create massive network traffic.

Basically it's a signing endpoint only. You create a page on your server that only signs the request but doesn't actually execute it. You then use the Location: https://api.foursquare.com/... header to redirect the user to a signed version of the page.

Note that all the signing happens on the server but the actual request is made by the client. No secrets are exposed since they're only used on the server.

Upvotes: 2

Quickredfox
Quickredfox

Reputation: 1466

It's ugly, yes. But my two cents:

If your needs for security greatly outweigh your needs for speed, then use a server-side proxy… It's basically what everyone does, most proxies are disguised in one way or another some are 3rd party services, some are tiny stand-alone rails/express/etc apps, some are just a endpoint on your existing app, some use google appengine, but kid yourself not, most people use a proxy.

If your need for speed greatly outweighs your need for security: Then go the "unsafe" way of exposing your key. It's not that bad really as along as you decouple data that's sensitive for your users that does not come from foursquare from data accessed via foursquare. You're only sharing some potentially fake geo data, some potentially fake place data and well, you get picture: it's not your user's financial statements, it's a big game called Foursquare. Worse comes to worse, change your API key once in a while.

IMO, in situations like these there is no middle ground, you have to balance it in your head so that one greatly outweighs the other (security vs speed). I have yet to see a situation where both are truly and honestly equally important and nothing can be traded-off.

Upvotes: 1

Related Questions