Best practices to share the access controls across back-end and front-end?

1k Views Asked by At

Project composition

I am working on a project composed of 2 front-end apps that request a back-end REST-API.
The organisation of our apps is pretty straightforward for now, but it is going to evolve soon.

Front-end:

  • Web app (React)
  • Mobile app (React Native)

Back-end:

  • API REST (in Ruby RoR).

Access Policies issue

Context:

  • The front-end apps needs to know for a given user which parts of the UI should be displayed based on the Access Policies.
  • It seems we have an hybrid model between RBAC + ABAC

For now we don't have a clear architecture to manage Access Policies, the business logic is spread across both apps and it leads to several issues :

  • Duplication of the Access Policies code on back-end + front-end (so we may end-up with the same code duplicated in 3 different parts of the codebase for the same policy).
  • Complex conditional on front-end: In some case we need to fetch multiple entities to decide wether or not the user can access a feature.

Solutions / Ideas

My initial idea would be to have the Access Policies logic centralized on the API. This way we prevent duplication, all the rules are in one part of the codebase, also it will scale better if we decide later to add front-end apps or even API microservices.

The missing point would be : How to share Access Policies across back-end and front-end ?

  • It is a good convention to expose such configuration from an API endpoint ?

Example :

route: GET /user/access_policies
response :

{
  author: {
    read: true,
    create: true,
    update: false,
    delete: false
  },
  books: {
    read: true,
    create: true,
    update: true,
    delete: false,
  }
}

Also can I expose specific business policies outside of simple CRUD verbs :

{
  books: {
    read: true,
    create: false,
    update: false,
    delete: false,
    deleteOwnBooks: true, // custom business policy (only delete the books owned by the current user)
    deletePublishedBook: true, // same

  }
}

Do you have other suggestions or best practices concerning sharing such Access Policies ?

Edit (5 Dec 2023)

We eventually went for the following solution: Exposes global permissions on the User resource.

type User = {
  id: string
  permissions: {
    author: {
      read: boolean,
      create: boolean,
      update: boolean,
      delete: boolean
    },
    books: {
      read: boolean,
      create: boolean,
      update: boolean,
      delete: boolean // Your user role allow you to delete books
    }
  }
}

To enable fine-grained resource permissions, we expose them directly from the specified resource.

type Book = {
  id: string
  name: string
  permissions: {
    // Can only delete own books
    // Can only delete published books
    delete: boolean
  }
}

Currently, this pattern suits our needs well. However, one aspect to be mindful of is performance, as it introduces additional computations. Typically, we address this by querying permissions on demand, utilizing dedicated query parameters in the Rest API.

1

There are 1 best solutions below

0
coda On

If the Authorization in general and ABAC in particular plays such an important role in your architecture then I would suggest looking into something like OPA. In case you can switch to RBAC, the easiest would be to configure roles in an Identity Provider and get them as claims in the JWT you pass around.