Skip to main content

Good practices

Access Control on endpoint is usually a bad pattern

A common practice (available even in basic tutorial) among developers is applying access control checks at endpoint level.

app.post(
'/articles/:id/publish',
requireAccess('publish', 'article'), // this denies access to endpoint without permissions
(req, res) => {
res.json(
articleRepository.publish(
getArticleIdFromRequest(req),
)
)
}
);

While it seems to be logical it doesn't scale well.

Imagine following example.

You need to generate a report of comments under articles. The report contains such information like:

  • article data
  • comment data
  • author data
  • maybe some moderator notes to comments?

You are you going to define a single (or even multiple) permissions for it an endpoint?

You can do

requireAccess('read', 'article')
AND requireAccess('read', 'comment')
AND requireAccess('read', 'comment-author') // etc

but that is fragile and whenever next person needs to modify the report (removing or adding new piece of data) will surely forget about changing endpoint permissions for it.

You can introduce special action like read-report

requireAccess('read-report', 'article')

but that doesn't take into account handling of permissions to comments, authors etc.

Ok - you might say - ignore them. Let's just access those data without any access control.

Well. That is totally unsafe and exposes you to a risk of disclosing potentially fragile data.

Solution?

Don't do access control on endpoint level but always on service level.

Service should be always responsible for making sure that current principal has sufficient permissions to access data.

Example:

  • ArticleService.findByQuery that assess whether principal can read articles
  • CommentService.findByQuery that assess whether principal can read comments
  • CommentAuthorService.findByQuery that assess whether principal can read comment authors
  • CommentAuthorNotesServices.findByQuery that assess whether principal can read notes about comment authors

That way it is guaranteed that no data leaks without proper permissions.

Use AccessQueryPreset to define access queries for domain

AccessQueryPreset is great for:

  • Defining set of possible access queries for domain
  • Creating policies for them
  • Ensuring type safety

More on that

Should operation on an entity that principal has no access to return Not found or Forbidden?

That is a very good question.

Returning Not found when referring to an entity, that principal has no access to, might be confusing since it is not really true. Entity exists but we refuse to give access to it.

Does that lie makes sense?

Sure!

Imagine attempt to reference user by email. Saying Forbidden indicates that user with given e-mail address exists. That allows for further data exfiltration and essentially guessing who is using the system. This is not a piece of information to disclose.

On the other hand opening google docs that you have no access to, displays a prompt that allows to request for access. That totally makes sense too. You've received a link from someone else but you cannot see that document and you want to know why.

Having that on mind there is no single definitive answer. Consider whether you can or should disclose information about entity.