REST API Design – Handling Multiple Resources and Authorization in REST API

apiauthorizationrest

I'm working on a service that forwards/unifies our API calls to external platforms/services.

For example, let's say that I don't want to handle all possible actions (create post, retrieve post, edit post, list posts) for Facebook, Twitter, LinkedIn, Google+ at each user-facing service. So the user-facing services calls this API forwarder with a message, somehow specify the service to post to and on behalf of which user is the request coming from.

To forward the call successfully, I need to identify:

  1. the calling service, for which I use the Authorization header.

  2. the service to which to forward.

  3. the user who initiated the request. I shouldn't store end user external service credentials (e.g. facebook tokens) in the calling service but in the forwarding service.

I have a few options on how to pass this data, and any input would be appreciated:

  1. Specifiy service and user as sub-resouces in URL

    /service/{externalServiceName}/user/{userReference}/Object/{objectId}

    E.g. GET /service/facebook/user/uid123/posts/post345
    This feels quite verbose to me.

  2. Use query parameters.

    /Object/{objectId}?service={externalServiceName}&user={userReference}

    E.g. POST /posts?service=facebook&user=uid123

  3. Overload the Authorization header with:

    Bearer: <OAuth token>, Service: <serviceName>, User: <userReference>

    Resulting in nice URLs (GET /posts/).
    This seems totally out of spec though. Turns out specifying multiple values for a header separated by comma is in the spec.

  4. Use custom headers:

    X-APIUnify-Service: <serviceName>
    X-APIUnify-User: <userReference>

    Again, with nice, short URLs (POST /posts/post345).

  5. Use a mix of custom headers and resources in URL:

    GET
    Authorization: Bearer: <OAuth token>, User: <userReference>
    /{externalServiceName}/Object/{objectId}

    This makes sense to me because: 1. the userReference is actually a second layer of authorization anyway. 2. The service is the resource that owns the Object. I.e. the same objectId can point to different resources in different services.

  6. Use JWT to pass the service and user, signed with the calling service secret.

    JWT.payload = {
    "callingServiceId": "5aafcd909c9a25054c3019e3",
    "externalServiceName: "facebook",
    "userReference": "uid123",
    "iat": 1521638763
    }
    Authorization: Bearer: <JWT>

Thank you!

Best Answer

We need to think in the best design API starting from the API objective.

Your API objective is:

service that forwards/unifies our API calls to external platforms/services

So, you start with posts endpoint:

POST /posts/

Body
{
    title: "Hello",
    text: "Hello World"
}

But this post can posted to Facebook, LinkedIn, etc and you need the user reference also.

You can pass this informations on the body, of course. But:

  • Mix exclusive platform attributes on same body. The Twitter, per example, does not have a title or some platforms has support for tags. This attributes will be ignored for platforms that not support them. The server will need to figure out at runtime what kind of platform he is receiving and looking if all required fields are there for that platform. The code could easily be a mess.
  • Client will handle the API complexity. As some attributes are exclusive for some platforms, the client needs to control what group of attributes he needs to send for each platform for the same endpoint.
  • Document this API will be a difficult thing to do. Imagine put a Swagger on that. Probably the client will have a hard time trying to figure out what informations is supported for each platform.
  • Support for new platforms will be harder. Maybe you would like to add a new platform that needs a new information to be passed together with the user reference (see my Slack example).
  • Versioning is not possible for each platform. You will not able to version each platform individually, what could be a nice feature to follow the changes of each platform.

And etc. You can think in a lot of possibilities and particularities of each platform.

As you can see, use a single endpoint is a risk.

So seems good to me separate in different endpoints:

/platforms/facebook/
/platforms/twitter/
/platforms/google-plus/

And the URL endpoint and body of each one can have his particularities. Divide and conquer.

You can understand this as /platforms/{platform}, but be careful. Threat this as generic can bring some problems if you need more flexibility on the URL endpoint. Per example, to create a post on Slack you need the platform, user and a channel:

/platforms/slack/users/slav/channels/rest-questions/

Thinking in this way, you can separate the platforms and can change each one without modify the others that are working. The GET and another HTTP methods are natural to use in this case too.

About the verbosity, is not a problem if this brings clarity about the use of your API.

Related Topic