fsprojects / FSharp.Data.GraphQL

FSharp implementation of Facebook GraphQL query language.
http://fsprojects.github.io/FSharp.Data.GraphQL/
MIT License
399 stars 73 forks source link

Authorization Middleware #157

Open Rajivhost opened 6 years ago

Rajivhost commented 6 years ago

Hi, How on the Giraffe example, can I made authorization based on policies. I mean something like this and this Giraffe PR?

Best regards, Rajiv.

johnberzy-bazinga commented 6 years ago

@Rajivhost The work @ivelten is doing in #155 should make the implementation of a middleware to handle authorization straightforward.

Namek commented 6 years ago

So I'd like to authorize user using mutation such as:

mutation signIn {
  email
  password
}

defined in F# like this:

  Define.Field("signIn", Nullable SignInResult, "Sign in user of given email with a password", [
    Define.Input("email", String)
    Define.Input("password", String)
  ], fun ctx _ ->
    let email = ctx.Arg "email"
    let passwordSalted = UTF8.sha1Hex <| (ctx.Arg "password") + AppConfig.Auth.passwordSalt

    Repo.validateUserCredentials email passwordSalted
    |> Option.map (fun user -> { id = user.id; email = user.email; name = user.name })
  )

Goal:

Based on this I want to create a token and put it into a cookie which happens to be a layer above GraphQL - it's HTTP (I use Suave).

Question

How can I return some calculation result to a layer above my signIn field implementation?

I don't see how middlewares would help with this.

johnberzy-bazinga commented 6 years ago

@Namek Middleware might not be appropriate for your scenario. I think it's technically possible since the result of Executor.AsyncExecute does return a metadata dictionary. A possible solution might be to thread a custom root value through the computation that contains the functionality required to sign in. e.g.

fun ctx _ ->
    let myRoot = ctx.Context.RootValue :?> MyRoot
    myRoot.Auth.SignIn(...)
Namek commented 6 years ago

Alright, thanks. I managed to do it using F# Reference Cells.

My root type (the context) is:

Ref<Session>

where Session is defined as:

type Session =
  { mutable authorizedUserId : int option
    mutable token : string option }

so I can do value rewrite as if Session was reference-based C# object:

do
  session := {
    token = Some <| createToken user.id
    authorizedUserId = Some user.id }

For completeness:

 Define.Field("signIn", Nullable SignInResult, "Sign in user of given email with a password", [
    Define.Input("email", String)
    Define.Input("password", String)
  ], fun ctx session ->
    let email = ctx.Arg "email"
    let passwordSalted = UTF8.sha1Hex <| (ctx.Arg "password") + AppConfig.Auth.passwordSalt

    Repo.validateUserCredentials email passwordSalted
    |> Option.map (fun user ->
      // a little dirty - 'do' in '.map'
      do
        session := {
          token = Some <| createToken user.id
          authorizedUserId = Some user.id }

       { id = user.id; email = user.email; name = user.name })
  )

The session can be passed through data argument when executing a query:

executor.AsyncExecute(query, variables = variables, data = session)
Rajivhost commented 6 years ago

I think, we need to provide this part as a middleware (based on this implementation for example).

nojaf commented 5 years ago

Hello, could I get some pointers on how I can add my bearer token info from my HttpRequest into the query somehow and then later on use inside a resolver to only return a subset of the data based on the content from the token?

johnberzy-bazinga commented 5 years ago

@nojaf executor.AsyncExecute takes a data parameter which is just an arbitrary value you can access from within a resolver function. e.g.

let myRoot = ctx.Context.RootValue :?> MyRoot where ctx is the first argument to the resolver function.

Your record MyRoot might look something like:

type MyRoot = { Prinicipal : IPrincipal; ... }

When using asp.net core identity you should be able to access the User property on HttpContext which is an IPrincipal and then pass that as part of the record you pass to the executor.AsyncExecute function.

Hope that helps. Let me know if you have additional questions.

nojaf commented 5 years ago

Hey, thanks for this pointer. I've tested this and it works fine. So what should I do/return if my user is unauthorized to query the info?

johnberzy-bazinga commented 5 years ago

@nojaf What you could do is just throw an exception in the individual resolver functions. That way it won't be all-or-nothing like the case of a 403 response in http. The user will still get the results for fields that they're authorized to see. Depending on the situation you might want to thread the user permission/roles through or encode enough information in the claims to avoid additional network requests when determining if the user has the privilege to see that information. Alternatively, you can cache or memoize the responses of fetching permissions for that user on a per-request basis. That decision will likely change depending on if the token is self-contained or a reference token. One caveat to watch out for is if the field is non-nullable and you throw an exception in the field's resolver function the error will propagate until it finds the first nullable ancestor field. The error propagation is done to ensure the response respects the graphql contract.

nojaf commented 5 years ago

Thanks again. Would it make sense to put the protected resources in a special Object definition under the root query?

For example:

query {
  protected {
     secrect1
  }
}

and then guard the protected block by some middleware?

xperiandri commented 2 years ago

@Rajivhost are you still interested in this feature? I have working code here https://github.com/xperiandri/FSharp.Data.GraphQL/tree/authorization_middleware Finalizing it as PR would be welcomed.

xperiandri commented 1 year ago

Only test is required