hi everyone, I'm using lacinia-pedestal and I'm with trouble to implement authorization and authentication on my project. You guys have any example of how to do that using lacinia-pedestal
so I can study a bit more?
I can give some high-level pointers but it’s hard giving details without sharing my entire codebase, which I can’t do 😞
Usually you would create a pedestal interceptor that deals with authentication, that is, given an HTTP request see if you can somehow lookup a user. You’d do this based on headers, cookies and looking something up in some kind of a DB, whatever you are using.
Once you have a user, then another pedestal interceptor can decide whether the user should actually have access to the graphql endpoint (perhaps graphql is only available to admins)
Then finally, inside every lacinia resolver you can access the context and figure out if the user should have access to that resolver.
That’s the high-level overview. There are some cool thing you could do to make the last step a little bit more automatic and/or data driven.
thanks for the overview! I was looking at the pedestal
documentation to see how to create interceptors like those you described (https://github.com/pedestal/pedestal/blob/master/samples/buddy-auth/src/buddy_auth/service.clj)
but in the end of the day, I imagined that I needed to code the authorizaton part on the resolver level
I think this should not be a problem, maybe a simple function to verify permissions would do it
The way we’re doing is that essentially the first step on the resolver is validating permissions using a boolean function. We are thinking about perhaps writing a macro to automate this a little bit or attaching some permissions in the .edn file that we use to create the schema, so that it’s more declarative.
cool. I got it. In order to add an interceptor to lacinia-pedestal
I just need to write the interceptor and add it on (lacinia.pedestal/service-map {:graph-ql true :interceptors my-custom-interceptor})
?
(defn prod-routes
"Return the prod routes for lacinia. Accepts an optional interceptors-fn that
can change the interceptors sequence for all graphql routes."
([] (prod-routes identity))
([interceptors-fn]
(let [schema (my.app.schema/load-schema)]
(lacinia/graphql-routes schema {:graphiql false
:interceptors (-> (lacinia/default-interceptors schema {})
(replace-interceptor ::lacinia/json-response json-response-interceptor)
interceptors-fn)
:get-enabled false}))))
This does two things — first it replaces the interceptor responsible to convert to json with a custom one, which I had to tweak. The second is it accepts a function that takes a vector of interceptors and returns another vector of interceptors.
This way you can prepend your auth interceptors
greaat!! thankx!
(def ^{:doc "Interceptors that ensure an API endpoint is auth-only."}
api-interceptors
[ring-middlewares/cookies
my.app.interceptors/attach-site
my.app.interceptors/authentication
my.app.interceptors/authenticated-api])
(defn lacinia-prod-interceptors
"Massage the default lacinia interceptor seq to be auth-only."
[interceptors]
(concat api-interceptors interceptors))
(defn lacinia-dev-interceptors
[interceptors]
(concat
[my.app.interceptors/dev-api-interceptor
my.app.interceptors/authenticated-api]
interceptors))
The cool thing with this is that I can reuse api-interceptors
for other non-lacinia endpoints.
dev-api-interceptor
is something that adds a fake user to the request so that GraphiQL works out of the box.
nice!
I just discovered https://github.com/workframers/artemis
reading the docs now, but it seems really nice!
ah, except it uses core.async 😞 hopefully there’s a way to just get a promise-based value as well
Hey, my company released artemis (though I'm not personally as familiar with it as some other folks here). Hit me up if you have questions
Wait, is core.async not the new hotness any more?
I prefer to keep core.async out of my projects. I don’t really find it useful for most front-end development, and would rather it returned a promise that I could choose how to plumb into my app (core.async, promesa, what-have-you)
especially since React will sometime this year have first-class support for promise-based data fetching via React Suspense
I see. I don't know the guts of artemis super-well, but it seems like it would be possible to have it deal with promises instead
yeah maybe I’ll open an issue
Is it considered bad practice to nest executes? I want to return the object after a create mutation
Some of the lacinia-pedestal code has evolved a little bit unevenly. At the core, though, is the ability to generate a interceptor pipeline that services GraphQL and GraphiQL requests, but there's an inject
function that is useful for adding new interceptors into the pipeline; this helps insulate your code from minor changes to the pipeline that may be added in later releases of lacinia-pedestal.
Order of execution can be an issue; the order in which keys are returned in the response is based on the order of the fields in the query BUT that may not exactly match the order of execution, if that makes a difference. There's a number of factors involved.
Whereas top-level mutations in a mutation request execute in a specific order, as per the spec.
Currently, there isn't a way for fields to share context sideways (to peers), only downward (to sub-fields). You could address this by having a in interceptor create and store a shared atom into the field resolver context.
Thank you for this explanation! I'm going to try and see if this works the way I'm thinking it will. If not I'll just call the same function I'm using for the resolver and manually add the fields I want to include in the response