5

On the server, is there any way to differentiate between an internal & external REST API request?

Why?

The reason I want to distinguish the two origins, is because I may, based on the advice, given by respondents, want to return a different data set, depending on who is trying to make the request.

Summary

My definition of internal maybe incorrect. In this instance, 'internal' means a request made from an XHTTP request from the same domain as the page processing the request.

An external call might be a user creating a Curl request from another domain.

For instance:

http.service.ts

INTERNAL ANGULAR 6 REQUEST


fetchLogin(formData: any): Observable<any> {
    let req = null;
    let headers = null;
    headers = {
      reportProgress: false,
      headers: new HttpHeaders({
        'email': formData['email'],
        'password': formData['password']
      })
    };
    req = new HttpRequest('POST', this.restApiUrl + this.restApiUrlEndpoint + '/oauth/', '', headers);
    return this.http.request(req)
    .map( (data) => {
      return 'body' in data ? data['body'] : null;
    })
    .pipe(
      catchError(this.handleError)
    );
  }

template.cfm

EXTERNAL COLDFUSION REQUEST


<cfset httpUrl = request.restApiUrl & request.restApiUrlEndpoint & "/oauth/">

<cfhttp url="#httpUrl#" method="post" result="result" timeout="30">
  <cfhttpparam type="header" name="email" value="foo@bar.com" />
  <cfhttpparam type="header" name="password" value="foo" />
</cfhttp>

Please understand that I have simplified these 2 code snippets to keep things clear.

When the request hits the server, how can I tell which request has come via XHTTP and which has been sent via CFHTTP [Curl]?

I am using Taffy.io REST API framework, so here is a simplified method, inside a 'resources' CFC, that I might use to process the request:

resources/oauthMember.cfc

<cfcomponent extends="taffy.core.resource" taffy_uri="/oauth">

<cffunction name="post">
  <cfset var local = StructNew()>
  <cfset local.data['email'] = "">
  <cfset local.data['password'] = "">
  <cfset local.requestBody = getHttpRequestData().headers>
  <cftry>
    <cfset local.data['email'] = Trim(local.requestBody['email'])>
    <cfset local.data['password'] = Trim(local.requestBody['password'])>
    <cfcatch>
    </cfcatch>
  </cftry>
  ...processing code
  <cfreturn representationOf(local.data) />
</cffunction>

</cfcomponent>

Adding an extra header to one of the calls is not viable, because this can easily be spoofed.

Any ideas?

Environment

Windows 2008R2 Lucee 4.5 IIS7+

Charles Robertson
  • 1,760
  • 16
  • 21
  • Could you provide a bit of context - why do you need to distinguish this? – jonrsharpe Feb 02 '19 at 10:11
  • @jonrsharpe I have added my reasoning to the question. Please could you remove the downvote, if this reasoning makes sense. Thanks... – Charles Robertson Feb 02 '19 at 10:38
  • *"I may"* - well, do you? If not, YAGNI. And if you do, different how? Should that actually depend on whether the request is "internal" or not as opposed to anything else you could discriminate? – jonrsharpe Feb 02 '19 at 10:42
  • @jonrsharpe "I may" depends on whether the advice I get from respondents, makes it viable or not. This is partly why I have asked the question in the first place... – Charles Robertson Feb 02 '19 at 10:45
  • @jonrsharpe I want to know whether it is common practice when building a REST API, to send back a data set that is conditional on where the request comes from, if that can be ascertained. – Charles Robertson Feb 02 '19 at 10:46
  • @jonrsharpe You must understand that I am relatively new to REST API architecture, so I am trying to find out what the 'red lines' are? – Charles Robertson Feb 02 '19 at 10:51
  • 5
    No, an endpoint should never return different responses based on the consumer. If there are two different responses, use two separate endpoints and require different authentication. A request can always by forged, regardless of being sent clientside or serverside. – Alex Feb 02 '19 at 13:17
  • @Alex Thanks Alex. That makes sense. I will make separate endpoints. – Charles Robertson Feb 02 '19 at 21:25

1 Answers1

4

In short, you can’t trust anything in coming request in public REST API data. Anything in request content/headers can be constucted by curl or custom program. You, probably, may have some trust in socket Source IP address, if your web server configured accordingly and has direct access to source connection address from socket itself. This is rare case nowadays, as usually web servers now located behind load balancers, firewalls with NAT tunnels, etc. But even in that case, Source IP address probably may be used only for some sort of whitelisting. Moreover, server that has this kind of access today, may lose it tomorrow, when your application may need load balancer to scale. Also note that user may have a HTTP proxies on a source path. So, using source IP as API criteria looks like bad practice.

Good practice is to create public API that is invariant to caller. API calls should concentrate on REST request body and headers provided to check for theirs validity, acceptability and security. Indeed, many API calls should be done in sequence like “login” -> sessionToken -> “API calls with sessionToken” -> “logout with sessionToken” (token invalidated). In that case important data (user Id, role, security context, etc) stored on server attached to sessionToken somehow. Note: many API architects do not recommend to store sessionToken in cookie, as it may simplify CSRF attacks (if no other countermeasures provided).

What you and probably many others want is to have “private” API to implement your web site functionality itself and “public” API to your customers. That’s a bit different story.

Public API you document, publish (at least to customers), promise to keep unchanged during long period of time.

Private API you may change at any moment and you may design it in a way that is comfortable to you. But if your site is live, anyone can still scan his own traffic and construct similar requests with curl (or something alike). You may, however, do some tricks to made abuser’s life harder, like issue some kind of JavaScript-calculated page tokens, use short-lived request chains etc, but this is not eliminated threat completely. So API cannot relay on that “nobody can construct such request” if all data for the request may be obtained from page and traffic by legitimate user.

Resume: Better to have public API for customers and private API to site itself and do not mix them.

Serge Ageyev
  • 437
  • 3
  • 8
  • Thanks Serge for this in depth information. It was exactly the kind of knowledge I needed. It certainly beats the negative response I had from Jon Sharpe, who downvoted this question, barely 10 minutes after I posted it. I am fairly new to REST API and am eager to gain an understanding of best practices etc. Thanks again. I am upvoting the answer and will wait to see, if I get any other responses before accepting the answer. – Charles Robertson Feb 02 '19 at 21:34
  • You are welcome, feel free to accept the answer if you think it was helpful. – Serge Ageyev Feb 19 '19 at 15:38