8

We have a connected app configured and working.

We have a web server oauth flow setup and have configured "api" and "refresh token / offline access" as the oauth scope.

We also created a separate user and profile and limited down the access of that profile to just those objects that we wanted to expose.

However, we noticed that granting the "api" oauth scope grants access to a bit too much then we would like. Things like chatter, content, custom settings etc are also visible. Potentially also other parts of the REST api that we rather would not like to expose.

Is there a way to further limit which parts of the api our connected app can use?

Or (maybe more suitable), is there a way to create (for example) a custom apexrest service and only expose that one and not the full REST api?

7
+100

This might not be what you're looking for, but I felt like I should float the idea out there anyways.

One good restrictive OAuth Scope is: Provide access to custom applications (visualforce). With this scope, all regular api calls will fail, and you can restrict access to just the few visualforce pages that you want that user/profile to have access to. Then you just have to turn your Visualforce into an api. For queries it is pretty straightforward:

Controller

public with sharing class VisualforceQueryService{

    public String QueryResult {get;set;}

    public VisualforceQueryService(){
        String QueryString = ApexPages.currentPage().getParameters().get('q');
        try {
            list<sObject> solist = Database.query(QueryString);
            QueryResult = JSON.serialize(solist);
        } catch (Exception e) {
            QueryResult = '{"error":"Line ' + e.getLineNumber() + ', ' + String.valueOf(e) + '"}';
        }
    }

}

Visualforce page

<apex:page controller="VisualforceQueryService" 
    docType="" 
    contentType="application/json" 
    applyHtmlTag="false" 
    applyBodyTag="false" 
    showHeader="false" 
    sidebar="false" 
    standardStyleSheets="false">
{!QueryResult}
</apex:page>

A query could be made through this page as so:

curl -H 'Authorization: Bearer <session-id>' https://analysis-dev-ed--analysis.ap1.visual.force.com/apex/VisualforceQueryService?q=SELECT+Id,Name,CreatedDate,OwnerId+FROM+Account

For other actions though (insert, update, delete, undelete), things are much harder. You can't do DML operations, future calls, queueable classes in a visualforce page constructor, and because the api is blocked for this user profile, you can't reach out to the api through an HttpRequest. There should be a way to use javascript remoting endpoints to perform those actions, but you would at least need to request the page to scrape up the csrf tokens before you can perform the remote action.


Okay, so for the sake of completeness, I got the DML operations to start working too.

Visualforce page

<apex:page controller="VisualforceDMLService" 
    showHeader="false" 
    sidebar="false" 
    standardStyleSheets="false">
</apex:page>

Controller

public with sharing class VisualforceDMLService{

    @RemoteAction
    public static sObject performDML(String Method, sObject so){
             if (Method == 'Insert') insert so;
        else if (Method == 'Update') update so;
        else if (Method == 'Delete') delete so;
        else if (Method == 'Undelete') undelete so;
        return so;
    }

}

To access the remote action here, I did two separate calls. The first one here returns the page with the CSRF token:

curl -H 'Authorization: Bearer <authorization-token>' https://analysis-dev-ed--analysis.ap1.visual.force.com/apex/VisualForceDMLService > res.txt ; node scrape.js res.txt

I used this to isolate the value:

var fs = require('fs');
fs.readFile(process.argv[2],'utf-8',
  function(err,data){
    var s = data.split('"');
    for ( var i = 0 , n = s.length ; i < n ; i++){ 
      if (s[i] === 'csrf') console.log(s[i+2]);
    }
  }
);

Then I could make the request to the Remote Action:

curl -H 'Authorization: Bearer <authorization-token>' -H 'Content-Type: application/json' https://analysis-dev-ed--analysis.ap1.visual.force.com/apexremote -d '{"action":"analysis.VisualforceDMLService", "method":"performDML", "data":["insert",{"Name":"new account","sObjectType":"Account"}], "type":"rpc", "tid":2, "ctx":{   "csrf":"<csrf-token>",   "vid":"06690000005c6hg",   "ns":"analysis",   "ver":39 }}' -H 'Referer: https://analysis-dev-ed--analysis.ap1.visual.force.com/apex/VisualForceDMLService' 

Ultimately, it's not that simple to implement, but if you really want to lock down access, it does give you that control.

  • great answer. I already had a feeling that that might be a direction to check. Now, I'm wondering though, would the visualforce scope also allow /apexrest/ services to be called? Probably not then? For working around the DML's in the constructor, I'm indeed curious if js remoting will work. We usually anyway already use remoting because our VF pages these days are based on AngularJS combined with remoting. Will give an update once I got the time to try it. – KoenVM Feb 17 '17 at 9:54
  • 1
    I also got confirmation from Salesforce that indeed today out of the box it's not possible to limit more granular the api scope to which a connected app would have access to. I will create an idea for this as I believe this would be very usefull. I'll post the link once I created it. – KoenVM Feb 20 '17 at 16:18
  • That would be a good idea. I've always wished that there was a configurable api scope where permissions could be selected similar to a profile, so that you could define which objects/fields/custom settings/etc. an app would have access to, and not just give the app the same level of permissions as the user. – martin Feb 21 '17 at 1:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.