Open Rajivhost opened 6 years ago
@Rajivhost The work @ivelten is doing in #155 should make the implementation of a middleware to handle authorization straightforward.
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 })
)
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).
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.
@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(...)
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)
I think, we need to provide this part as a middleware (based on this implementation for example).
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?
@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.
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?
@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.
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?
@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.
Only test is required
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.