I'm building quite complex REST API. The whole API is protected with authentication.
Some of the resources (like, let's say, Person
) should be accessible for anyone in the system, however I'd like to "hide" some fields for specific user's roles.
Let's say the Person
resource has following fields:
FirstName
LastName
BirthDate
Address
I'd like them all to be visible for users with HRManager
role, hide Address
for JuniorHRManager
and leave FirstName
+ LastName
for everyone else.
Is that RESTful way to just remove fields from the response based on rules applied for the role which logged in user has? This would be most simple to implement I guess (since I'm using an excellent ServiceStack which has global response filters), yet I'm not sure if that doesn't break the REST rules?
The only other way I've so far thought of is creating role-specific Resources (like PersonForHRManager etc.) however this would be ridiculous as the system is supposed to have variety of combinations of visible & hidden fields for roles.
The API should always return sensible HTTP status codes. API errors typically break down into 2 types: 400 series status codes for client issues & 500 series status codes for server issues. At a minimum, the API should standardize that all 400 series errors come with consumable JSON error representation.
The GET /api/v2/users/{id} endpoint allows you to retrieve a specific user using their Auth0 user ID. This endpoint is immediately consistent, and as such, we recommend that you use this endpoint for: User searches run during the authentication process.
Although URLs containing parameters within the query string do themselves conform to REST constraints, the term “REST-style URL” is often used to signify a URL that contains its parameters within the URL file path, rather than the query string.
I agree with your approach, the response filter would probably be the best solution to do this; and simply mark up the response DTO with an attribute describing the required roles. I haven't seen a better way to do property level permissions.
It is perfectly acceptable to remove properties from response based on roles, if it is a public API just make sure to document the properties people can expect back in each role.
Response Filter, in your AppHost:
this.ResponseFilters.Add((req, res, dto) => {
// Get the roles you are permitted to access. You will need to store these in the request Items collection
var roles = (from r in req.Items where r.Key == "Roles" select r.Value).FirstOrDefault() as string[];
// Get the type of the response dto
var dtoType = dto.GetType();
// Loop through the properties
foreach(var property in dtoType.GetPublicProperties()){
// Ignore properties that are read-only
if(!property.CanWrite)
continue;
// Get all the role attributes on the property
var attributes = property.GetCustomAttributes(typeof(RequireRoleAttribute), false) as RequireRoleAttribute[];
// Get all the permitted roles
var permittedRoles = new List<string>();
foreach(var attribute in attributes)
permittedRoles.AddRange(attribute.Roles);
// Check if there are specific permitted roles assigned to this attribute
if(permittedRoles.Count != 0)
{
bool permitted = false;
// Check if check require role against roles we may have.
foreach(var role in permittedRoles){
if(roles.Contains(role))
{
// We have a matching role
permitted = true;
break;
}
}
// No permission to the property
if(!permitted) {
var type = property.GetType();
// Set the field to a default value.
property.SetValue(dto, null);
}
}
}
});
The attribute:
public class RequireRoleAttribute : Attribute
{
public string[] Roles { get; set; }
public RequireRoleAttribute(params string[] roles) { Roles = roles; }
}
On the DTO:
[RequireRole("Spiderman","Superman","Batman")]
public string Address { get; set; }
Notes:
request.Items.Add("Roles", string[])
I hope this helps.
I think you got it right. RESTful services is nothing more than simple HTTP requests. Therefore, you have to protect http resources as a whole. I see two possible strategies:
Implement different RESTful service for each role, which is the strategy you suggest. This is not very easy to maintain, as you probably understand.
Implement one RESTful service which returns for all roles but you would have to check for authorization while forming each property value. For example, when you shape the value to be returned for the property Address
on the server side, you will have to check the user's role. If the user is an HRManager
you would return the right value. If the user is a JuniorHRManager
you would have to return the specific property empty or with a respective message indicating that the user is not allowed to access the specific property. This strategy could be easier to maintain, depending on the server side technology used to implement the web service (I don't know which you are using). In .NET for example you could use attributes for each property indicating which authorization roles have access to a specific property. If you use .NET, this and this tutorials could provide some extra guidance.
Hope I helped!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With