Prisma1: Consider Auth0 permissions/roles for permission system

Created on 15 Mar 2017  路  21Comments  路  Source: prisma/prisma1

Most helpful comment

Proposal for improving JWT token support for authentication and authorization

I tried to combine the information/requirements from this and other FR's into a somewhat coherent proposal, in order to help this move forward. Feel free to comment to it, and I will update this proposal based on the comments.

Support token invalidation

In order to support additional claims, it's important to have a mechanism in place that allows you to invalidate a JWT token. Requested here: https://github.com/graphcool/feature-requests/issues/230

Support additional claims in token

In order to make additional information available to the Graphcool system in tokens, it's important to adhere to a standard token structure. OIDC id_tokens seems the most used spec for this. Requested here: https://github.com/graphcool/feature-requests/issues/344

Add authorization claims to tokens

Using tokens as a mechanism to specify authorization claims (groups/roles/permissions), the token generation mechanism needs to be extended to support this. Out of the box, Auth0 tokens already contain this information, but if a user wants to 'roll his own' using Graphcool, he needs a way to put these claims in the token. I would propose extending the generateAuthToken function of graphcool-lib to pass in these claims. The method already contains a payload argument, but it's not implemented yet.

Expose these claims in Permission Queries

So, once we get the ability to add claims to tokens, and follow a standard spec, the next step is making these claims available for Permission Queries. This is requested here: https://github.com/graphcool/feature-requests/issues/251. If these claims are available in PQ's, they can be used to 'map' any roles/permissions to the actual permission queries on Graphcool Types. This causes the least overhead.

Apply permissions from claims automatically?

Taking this one step further, if the claims follow a standard format, it might not be necessary for basic use cases to specify a permission query. Adding to the 'authentication required' checkbox something like a 'authorization required' checkbox should theoretically be enough, if the claim specifies for example the permission to 'create posts'. However, I have not investigated the claims structure in detail, so I don't know if it's possible to do this. It would really cut down on administrative overhead if this was possible though...

All 21 comments

This is a big one to me: most apps have quite some different roles/permissions/groups, and there's a reason they have this: you want fine-grained access to fields/types/... . It would really make sense to me to look for permissions, roles and groups fields in a (pre)defined object (authorization seems rather logic) in app_metadata, and use these in the authorization/access control-part of graphcool.

Putting auth items (roles / permissions / groups) within app_metadata is not a great idea. The token you receive from Auth0 can only be so big, which puts a hard cap on the number of auth items a user can have.

Auth0's roles/permissions extension also isn't very good. In fact I'm investigating graphcool as an alternative.

For your use case, this may be true. In our case, we want to keep the authorization system that we can implement in auth0. It is a no-go, at this point, to implement the authorization in graph.cool. The token can be quite large, and certainly large enough to add roles and permissions in about all the cases I'm using auth0 for. Why would one not let the user decide what the best solution is for the use case?

(To me, it's a pretty bad idea to think that one can solve certain concept with one solution for all cases. The more open and flexible services are, the more one can design properly, without being limited by the services used.)

Thanks for chiming in @gilbert and @kurt-o-sys :+1:
I'd like to mention that we're running a beta program for the schema extensions feature that allows you to roll your custom authentication. Here's an example with Auth0.

I'd love to hear your opinions on this approach. You could sync the permission setup at Auth0 to Graphcool upon account creation, and have another custom mutation that syncs it after account creation. This permission data will be persisted into Graphcool and can be checked in a permission query. What do you think?

My pleasure :). I'm not in favour of having 2 places where to truth is stored. It gives rise to a number of issues and they can get out of sync, and if it _can_ happen (networks do fail, disks do get full, people make mistakes etc), it will... and probably at the most unappropriate moment.
There are a few problems I have with that approach (besides what I just mentioned):

  • there's another webtask to setup (and hence, to maintain) - unnecessary overhead
  • it supposes to a large extend that auth0 knows what authorization scheme will be used, or roles/permissions/groups have to be mapped to a graph.cool scheme. This also means, changing authorization schemes must happen in 2 places, which is not really nice. That's the problem with 2 places of truth.

However, if it is too hard to just pass the JWT content inside graph.cool filters, I guess there are only 2 options for the users:

  1. adapt and live with it
  2. find another solution

In my case, it depends on the project.

I think this boils down to either:

  • exposing all token fields in the permission queries, or
  • having the FR to implement permissions as functions.

I'm very much in favor of both.

The problem isn't having two places to store truth, but duplicating business logic. Auth0 has an API, so you could query user data to avoid that duplication (using those schema extensions). The only downside is having to write the webhook, as @kurt-o-sys mentioned.

For the benefit of future internet readers, another downside to storing authZ in Auth0's id_token is that doing so duplicates your truth, which might cause problems. For example, if you remove admin status for a user in your source of truth, that user will still have admin rights until their token expires. Because it's a JWT / JWK, you can't remotely discard it (at least, not very easily).

Personally, if I'm going to store authZ data in a second place, it might as well be in graphcool to take advantage of their nice-looking "permission queries" feature, which would not suffer from any of the problems I mentioned. If I can get it all to work, that is...

There are more downsides:

  • using graph.cool is more difficult than using auth0 (for many non-IT) people. This makes user management possible in auth0, but not in graph.cool for many use cases. So leaving the user management in something that's easier to use, had advantages in these cases.
  • There's a difference between the materials (JWT) and the run-time behaviour (the fact that you can't revoke remotely). That's how it is if you use JWT. It's up to the business to decide whether they are ok with that run-time behaviour or not. It's not up to me (or anyone else). It's my job to give the materials I can use and explain their behaviour. It's up to the business to decide what they want.
  • You are never sure that things are synced. Things can (and will) go wrong.
  • You have not only to write, but also maintain the additional webhook.

I don't get your point that storing the data in graph.cool solves the problem that you can't discard remotely. If JWT (the material) is used for authorization, storing in a dozens of places (building a JWT-factory a few times) doesn't change anything. You still can't discard the JWT remotely. If you remove the admin status, the user will still have it's rights until the JWT is expired. (That's why one uses expiry dates, and renewable JWT's, renewing it every x hours or minutes).

So, I can to some extend agree that it mightbe better to have authorization inside graph.cool - although this may lead to tight coupling, which is not really what I usually want. However, there's the reality as well, and it's up to the users (my clients) to decide what they want. They don't want a graph.cool interface for user management, they are ok with the auth0 interface. (ofc, I could make a dashboard on top of graph.cool etc, but why would I do that, if there's something like auth0 which is good enough and has really nice features?)

Again, it depends on the project, but in many cases, many 'businesses' (mainly small non-profit organizations) are ok with the auth0 user management (incl. authorization). They are not ok with a graph.cool interface. That's just the reality. Having the ability to choose is more important than theoretically/technically the best solution to me.

@kbrandwijk exactly: being able to choose! It's not up to graph.cool to decide how applications should be designed. They offer an amazing service, and the more open and extensible it is, the better. At least, that's my opinion :p.

This makes me think: there's a third option:
graph.cool provides a well-designed user management/authorization interface (and optionally authentication). In this case, there's a third party involved (the business/user management admin), which should be reflected in the permissions and authorization of graph.cool itself.

@kurt-o-sys I tried very much not to word my comment as attacking your decisions in any way, since that was not my intention. I wrote strictly about technical content. Sorry if I didn't do so well enough.

Putting authZ in graph.cool solves the "lingering admin status" problem because you are no longer storing your authZ inside the token. When you remove a user's admin status from graph.cool, that fact will be reflected immediately; on that user's next request, graph.cool's Permission Query feature will check against their current admin status and find it to be demoted.

@gilbert :) I'm not offended, I'm just putting the reality: if the user (very often a low-in-budget non-profit in my case) doesn't want to use the graph.cool interface for user/permission management, but likes the features of auth0 and can handle that interface... well, than auth0/JWT is what's to be used. And if JWT is used, you can't solve the lingering status problem. If graph.cool has an interface for to add and remove roles/permissions/groups for a 'user admin', well, problem solved. It's not there, though.
I'm not sure how I can solve the interface issue for user/permission management with graph.cool. If you have a solution for this, well, please provide that one to me. I'm don't know how to do it.

Hi guys, have a look at this: https://auth0.com/blog/blacklist-json-web-token-api-keys/

That takes care of blacklisting tokens, so you no longer have to worry about having 'old' authorization in the tokens. Now, with all roles and permissions managed in Auth0, and the token guaranteed to be valid, all the Graphcool permission system does is apply those permissions, if the information from the token would be available in the PQ. Applying the roles/permissions from the tokens in a PQ is the technical side, for management of roles/permissions you can either use the Auth0 interface, or roll your own through the use of the Auth0 API. Would that work?

Now, with this approach, the PQ don't care where the information in the token comes from. One might also roll their own roles and permissions system inside Graphcool, or put the same information in the auth token in any other way. As long as it's in there, you can use it in the PQ, and you maintain the maximum amount of freedom for all users of Graphcool.

Exactly. That was the idea: pass JWT fields to PQ... so to give the users freedom how to implement authentication and authorization. Thx @kbrandwijk

@gilbert Do you also think that storing the roles/permissions info in the token (wherever it comes from, Auth0, Graphcool, or any other authorization provider), combined with the ability to blacklist/revoke tokens, and having access to the roles/permission information from the tokens in PQ's to implement the technical side solves all issues that were mentioned above?

Proposal for improving JWT token support for authentication and authorization

I tried to combine the information/requirements from this and other FR's into a somewhat coherent proposal, in order to help this move forward. Feel free to comment to it, and I will update this proposal based on the comments.

Support token invalidation

In order to support additional claims, it's important to have a mechanism in place that allows you to invalidate a JWT token. Requested here: https://github.com/graphcool/feature-requests/issues/230

Support additional claims in token

In order to make additional information available to the Graphcool system in tokens, it's important to adhere to a standard token structure. OIDC id_tokens seems the most used spec for this. Requested here: https://github.com/graphcool/feature-requests/issues/344

Add authorization claims to tokens

Using tokens as a mechanism to specify authorization claims (groups/roles/permissions), the token generation mechanism needs to be extended to support this. Out of the box, Auth0 tokens already contain this information, but if a user wants to 'roll his own' using Graphcool, he needs a way to put these claims in the token. I would propose extending the generateAuthToken function of graphcool-lib to pass in these claims. The method already contains a payload argument, but it's not implemented yet.

Expose these claims in Permission Queries

So, once we get the ability to add claims to tokens, and follow a standard spec, the next step is making these claims available for Permission Queries. This is requested here: https://github.com/graphcool/feature-requests/issues/251. If these claims are available in PQ's, they can be used to 'map' any roles/permissions to the actual permission queries on Graphcool Types. This causes the least overhead.

Apply permissions from claims automatically?

Taking this one step further, if the claims follow a standard format, it might not be necessary for basic use cases to specify a permission query. Adding to the 'authentication required' checkbox something like a 'authorization required' checkbox should theoretically be enough, if the claim specifies for example the permission to 'create posts'. However, I have not investigated the claims structure in detail, so I don't know if it's possible to do this. It would really cut down on administrative overhead if this was possible though...

Nice overview, @kbrandwijk

what's the status of this one? Still in discussion? (It's a deal breaker to me: I can't make people work with graphcool for user and roles management, but it's not a problem for them to use auth0.)

This could be implemented at the moment in two different ways: using custom Auth0 integration using resolver functions (where you would create/update groups/permissions in Graphcool automatically on login, so you could access those groups/permissions in your permission queries), or by using an API Gateway for your permissions based on express-jwt-permissions.

@kbrandwijk
Solution 1:

where you would create/update groups/permissions in Graphcool automatically on login

How exactly should I do that? I need access to the JWT content and validation to do so, right? And ones a use has it's JWT, there's probably no need for login each time the user uses the application. So there would be a problem when roles are revoked and the user doesn't need to re-login? (Auth0 refresh tokens handle this situation, in the sense that a new token is automatically issues every xx minutes, with updated roles etc.)

Solution 2:

by using an API Gateway

Possible, of course, but that makes it hard to put permissions per field/table. That's why the authorization of graphcool makes a lot of sense: it's much more fine grained. Unless I'm missing something here as well.

Just wondering: what exactly is the problem with exposing a JWT (or, more general, the authorization-http header) in the permission queries? It makes perfect sense to do this: the http authorization header contains, well, authorization info. Hence, it makes about the same amount of sense to expose this information to the authorization logic of an application.
What is withholding graph.cool to implement this?

This issue has been moved to graphcool/graphcool-framework.

Was this page helpful?
0 / 5 - 0 ratings