StellateHQ / fuse

Fuse: The fastest way to build and query great APIs with TypeScript

Home Page:https://fusedata.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RFC: Authorization

mxstbr opened this issue · comments

Summary

We are considering adding authorization as a first-class citizen to Fuse.js.

Proposed Solution

None yet. It's unclear whether there is a good use case for this, as the common scenario will be that the underlying microservices & third-party APIs already have to handle authentication & authorization.

However, in my personal experience, handling authorization at the GraphQL level (in my case simply through making sure that each resolver has an authorization check) was terrific to work with and kept our system well secure enough. We could e.g. require each node to have an authorize check that explicitly has to mark things as public via authorize: () => true so that it's explicitly note.

Note: Pothos has two related plugins: Authz & Scope Auth.

I like the authorize function but how do you plan to handle field level authorization?

Nothing prevents us from having t.field({ authorize }) I however am more concerned with the authorization inheritance problems that could show themselves. For example, when someone submits an order, that's their order only they can look at it so on the node we would authorize contextually. Let's imagine that the above API is also used by super-users that go about shipping orders, they aren't that user so there's an additional option for authorization all of a sudden that becomes contextual to the field that has produced this Order.

This means that node() might not be the best place to define authorization or it could be a good place to define the least restrictive authorization and define the most restrictive authorization on a field-level. 😅 to leave my thoughts out here

Yes @JoviDeCroock . Your example is right, most of time we keep the same endpoint and handle the authorization if you are admin ou owner of this resource to avoid duplication of code.
Node could be a good place if it is getting the context of the user and the information about the field.
Field level authorization is mostly for hidding. Let's take the email field of user for example,

  • if he make it public you can see it it
  • if he make it private but it is the owner or he is admin you can see it
  • else we return null

Coming back to this, I think I personally prefer us using the pothos auth-scopes plugin as that allows us to have a convenient battle-tested way (we use this at stellate!) of defining authorization on a by-field, by-type, ... level.

The issue we'll face here is the typings aspect as we did fork the SchemaBuilder creation to introduce our own opinions. Maybe we can override it through global-types, this is to-test.

The general thinking here would be that we give the power to the user to define a set of scopes with a function

import { defineScopes } from 'fuse'

defineScopes((ctx) => ({
  isLoggedIn: () => !!ctx.user,
  isAllowedToReadLaunch: async (launchId) => {
    // do async logic
    return true
  }
})

Now folks can add this to their field and/or type-definition by means of authScopes: { isLoggedIn: true, isAllowedToReadLaunch: l => l.id } where we can also specify a function to