Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization versus Authentication #73

Open
jkachmar opened this issue Dec 6, 2017 · 3 comments

Comments

@jkachmar
Copy link

commented Dec 6, 2017

This intentionally overlaps/duplicates #5, since that issue is relatively old and servant-auth, as well as Servant itself, has changed substantially since then.

Right now it's looking like servant-auth is going to become the blessed form of authentication handling in Servant, and one of the areas that's really lacking right now is how to handle route authorization through a custom combinator.

I don't really have a great solution in my mind, except that I'd like to have a combinator - or documented process of writing some combinator - that accepts the result of servant-auth's authentication and can then authorize access to a route based on the information there.

It'd be particularly useful if there was a documented method to use such a combinator inside a custom application transformer stack, as my main motivating example would be decoding the authentication token and using a DB connection in some ReaderT stack to check the permissions for that user's ID.

@alpmestan

This comment has been minimized.

Copy link
Contributor

commented Dec 6, 2017

This comment is in no way an official answer, it's just my perspective on how to add authorization in servant applications.

In my opinion (might be controversial? I don't know), the API type should not contain anything about authorization. Why? Well, I think that the API type should only contain "public information" about the API, more specifically all the information a client needs to send well-formed requests to that API. However, in my opinion the client should not have to know what the different authorization levels are and which ones are needed for which route etc. All the client should do is send some credentials, and then the server will perform some check on those and allow or deny access to a given route.

If we follow this perspective, then the most straighforward way to add authorization to your server would be to have some helper function userAtLeast :: User -> AuthorizationLevel -> Handler () which would check your user's authorization level and compare it against the required one. If sufficient, you just return (), and if not, you throwError .... You could then use it like:

someHandler user = do
  userAtLeast user Administrator
  -- what follows is not executed if the user is not an admin
  ...

I've used this approach, it's dead simple and can be as flexible as you want it to be.

... But I also did try once an approach where you would state that you require 1/ an authenticated user 2/ with at least some given role/authorization level. So in servant-auth it would be something like:

data Role = NormalUser | Moderator | Administrator
data User (minRole :: Role) = User { ... }

-- let's say we're using JWT
type API = Auth '[JWT] (User Administrator) :> Get '[JSON] Int

and then the auth check would only go through if the user is confirmed to be an admin. The main downside is that you have to write the authorization logic in the "auth check". Another one is that this type level param to User can be annoying/awkward to work with.

Finally, to my knowledge it's a bit hard to implement a solution like the one you describe, with our existing authentication combinator and a separate one for authorization that would reuse data from the authentication one. I mean that in the technical sense. Since each combinator is defined individually by its effect on an API type, it's hard to state that you want something particular to happen when both occur in the description of some route or API. If anyone ever gets something like that to work, I think I would insist a lot less on my conceptual argument against having authorization data in API descriptions. =P

Let me know what you think and if you have questions about how this all fits with custom monads for handlers and database connections and what not.

@jkachmar

This comment has been minimized.

Copy link
Author

commented Dec 6, 2017

That all sounds excellent, thanks so much for the thorough response!

I'll probably go with the straightforward example you've provided since it's really all I need at the moment, but it's nice to know that there are some other options easily accessible.

I'm fine with closing this issue then; would it also make sense to close #5?

@alpmestan

This comment has been minimized.

Copy link
Contributor

commented Dec 7, 2017

Well, it depends on whether people think my two suggestions are acceptable solutions, at least until someone comes up with a better one. In my opinion the "authorization problem" is indeed solved in servant (but the solution is under-documented). If "most people" agree, we can definitely close those issues (and just make sure we document the solutions in things like the WIP cookbook effort).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.