2

Here's an example:

GET /posts
POST /posts
GET /posts/{post_id}
PUT /posts/{post_id}

This api is being used by SUPER_ADMIN as well as CONTENT_WRITERS. Its working great. If user is logged in as a SuperAdmin, he sees all the post, and if user is logged in as ContentWriter he sees only his post.

But, now the requirement is added, where SuperAdmin should be able to do CRUD opertations on behalf of any ContentWriter.

I added these APIs for that:

GET /user/{user_id}/posts
POST /user/{user_id}/posts
GET /user/{user_id}/posts/{post_id}
PUT /user/{user_id}/posts/{post_id}

Its working fine too. But the problem here is, now there are too many APIs/Controllers to maintain. Although, the controllers use the same service for that.

My question is, am I going to a right direction? Or is there any other best practices for this?

UPDATE 1

The idea here is somewhat similar to impersonation.

  • 1
    This doesn't seem right to me. doesnt post have a userid property? – Ewan Oct 15 '18 at 11:43
  • 1
    I'd rather add a "on-behalf-of" when authenticating, artificially offering the same visibility rules as the user. – dagnelies Oct 15 '18 at 11:43
  • 1
    By too many are we speaking about thousands or just dozens of new methods? I do ask this because before re-factor something that it's proven to work (solving the problem) you have to find out whether the cost/benefits of a new solution outweighs (or not) the cost of the current one. – Laiv Oct 15 '18 at 11:43
  • yes "too many" seems to be "the exact same number as before" – Ewan Oct 15 '18 at 11:44
  • 1
    I don't think you have provided enough details on the requirement. In my experience this is typically handled via impersonation, where the admin actually authenticates (through some admin mechanism) as the user and acts on their behalf. In that case all you would need to do is add the ability to impersonate users. – Dan Wilson Oct 15 '18 at 13:41
2

A requirement to allow admins to impersonate users shouldn't require any more API endpoints than already exist in the system. This should be handled with additional form fields on your create/edit POST/PUTs rather than entirely different endpoints. You would need to add some validation that these fields are only populated when the request comes from an admin, or to ignore them for non admins. You should already have the authorization mechanism in place to recognize an admin request from a non admin, this functionality can be viewed as a slight extension to that. While not required for fulfilling the business need, it would also be a good idea to modify your database/persistence layer to track both who made an update and who should be displayed as an update. This will prove useful should an admin abuse their power.

| improve this answer | |
  • so are you insisting on sending user_id as a request parameter in the payload? – TheManish Oct 15 '18 at 12:38
  • @TheManish the user_Id of the impersonated user should be in the payload. – Ryathal Oct 15 '18 at 14:31
2

But as far as I can tell, what you are really trying to do is analogous to having Alice invoke

sudo -u bob ls /home/bob

I think you can reasonably argue one of two ways

First, that this is a different resource; it shares a common set of representations with ls /home/bob, but the authorization rules are different. On the web, that would mean two different identifiers

/home/bob
/su/bob/home/bob

In effect, we have a new protocol, so we create new resources to support that protocol.

The other argument is that these are the same resource, and the difference between the two cases is the Authorization header. That feels right? but I can't find any examples that do this.

You can find some examples where people have implemented a similar idea using custom http headers. For example https://www.linkedin.com/pulse/jira-rest-api-user-impersonation-marjan-sterjev/

Because of the lack of clear precedent, I would recommend continuing with the first approach.

| improve this answer | |
  • Is there a specific downside to creating a custom header? Standards are great but I'm not sure it makes sense to create a parallel API because there isn't an existing one to support your need. – JimmyJames Oct 15 '18 at 13:38
1

I'm not an expert at this, but since you are trying to make requests on another users behalf, from the APIs point of view the requests come from that user, not from the Admin.

Have you considered adding an admin route that allows the admin user to obtain a session token for another user? That will allow the admin to send the requests to the endpoints you already have using that other users token.

| improve this answer | |
  • 1
    but in that way, I won't be able to track which admin did the change. I have to track that as well, since there are multiple super_admins – TheManish Oct 15 '18 at 12:37
  • then you can have 2 tokens sent via headers in the http request. One for the user making the reques and another for the user on whos behalf the request is being made. Use the same auth mechanism on both to id the users. It goes without saying that a middleware of somesort is needed to ensure that the user that is making the request doesnt have higher priviliges then the user on whos behalf the request is made. – Alexander Oct 15 '18 at 16:39
  • sorry i ment lower priviliges – Alexander Oct 15 '18 at 16:39
  • I like this concept. I will see if I can achieve this. And get back to you. Thanks @Alexander – TheManish Oct 15 '18 at 17:13
0

I think a key thing will be your exact requirements around what it means to "post on behalf of another user"

But, assuming its just the author byline I would simply have a property on the Post object

class Post
{
    public string AuthoredByUserId {get;set;}
    ....
}

Now in your controller you can check whether AuthoredByUserId is the same as the user submitting the post and either allow it for super users or disallow it from normal users.

Having said that, personally I would still add a seperate endpoint simply so I can use built in authentication per endpoint methods, rather than having to code an authentication check eg.

[Authorise(Roles="ContentWriter")]
public Action AddPost(Post)
{
    ...error if AuthoredByUserId != the users Id
}

[Authorise(Roles="SUPER_ADMIN")]
public Action AddPostForAnotherUser(Post)
{
    ..ignore the userid validation
}
| improve this answer | |

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.