Francesco

Francesco

Absinthe seems to work with an Absinthe.Resolution instead of Plug.Conn, however this makes interacting with Plug.Conn quite difficult, the only way seeming to be to hook into the before_send callback of Absinthe.Plug.

In cases where you need to change the session or cookies based on the return value of a resolver (for example a login mutation), then it’s not immediately obvious how to go about doing that. My current approach is to use a piece of middleware to put the resolution value on the resolution context, and then in the before_send callback grab that information and do whatever I need to do with Plug.Conn.

So my questions are:

  1. Why doesn’t Absinthe expose Plug.Conn for use in mutations?
  2. Is my current method of interacting with Plug.Conn (using the resolution context and before_send callback) the recommended best practice?

Showing Posts 1 to 8

axelson

axelson

Scenic Core Team

I think most people (including me, and others from what I’ve gathered here and in the slack channel) handle login outside of graphql, that way all the graphql requests are fully authenticated already. Then it’s just a matter of adding what authentication info you need into the absinthe resolution context.

Francesco

Francesco OP

Thanks for the quick response, I had thought that might be what people were leaning towards.. It just seems odd that Absinthe doesn’t provide you with a nice way of handling this situation.

hauleth

hauleth

GraphQL, in theory, do not require HTTP. Sometimes it is via WebSockets, but it can be over any other protocol you want. Nothing in standard prevents you from serving GraphQL queries over SCTP with ETF as a transport format.

Francesco

Francesco OP

Ahhh that’s interesting, I hadn’t realised this.

In that case could we not (and I’m totally spitballing here so I may be way off base) provide some kind of generic way to feed in a Plug.Conn or it’s equivalent for another transport protocol? Maybe a middleware macro where Absinthe can pass in whatever struct represents the protocol currently being used (e.g. Plug.Conn) and you can pattern match the arguments provided in your middleware module callback to do Plug.Conn specific work?

For example:

    field :login, type: :user_result do
      arg(:email, non_null(:string))
      arg(:password, non_null(:string))

      resolve(&Accounts.login/3)
      after_resolve(ScribeWeb.Schema.Middleware.SessionCreator)
    end

where SessionCreator is a middleware module with a callback accepting Plug.Conn or it’s equivalent.

This still isn’t great though because it means you have to split up work between the resolver and the middleware. This is annoying when the middleware can have multiple outcomes depending on what happens in the resolver. For example in Accounts.login I have two successful outcomes:

  1. The user is already logged in so they don’t need a new session/refresh token
  2. The user isn’t logged in but the details provided are correct so new a session/refresh token pair are created

In this case I would need some way of telling the middleware whether or not I would like the session and refresh token to be created and placed on Plug.Conn, which isn’t obvious. One way would be to put an action on the resolution context (as a keyword list where the value indicates the type of action, e.g. :update_session) that the middleware would be listening for. But again, this feels a little off.

So maybe instead there could be a field on Absinthe.Resolution (e.g. after_resolve) that accepted a tuple containing a keyword mapping to a transport protocol (e.g. :http, :websocket) and a function (or multiple functions) that will be called after the field has been resolved. If the protocol specified was being used then these functions would be called with the relevant structs being passed in.

I know all of these potential solutions force you to think about the transport protocol in the schema, where ideally the schema should be protocol agnostic. However I don’t really see what benefit not allowing you to do this brings, considering that this seems to be a somewhat common use case. Absinthe already has a before_send callback that passes in Plug.Conn to try and tackle this, but I just think it’s a bit clunky as the work is being done very far from the code that is causing the work to happen. Again could be way off base so I’m interested to hear what you think.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Absinthe.Plug has this, Absinthe does not. Absinthe will continue to insist that GraphQL operations are handled without regard for transport. Absinthe.Plug is welcome to map Plug.Conn data to the context and map context info back to the conn, and I’m happy to consider proposals that make that more streamlined.

If you mean Absinthe middleware here, consider just ditching a resolver for that operation and just using middleware. resolve is a thin layer around middleware anyway.

Francesco

Francesco OP

Rigggghhht, so is Absinthe.Plug’s purpose just too map Plug.Conn data to context and back again?

As a possible solution, could you have an extra field (in addition to context) on the resolution that allows you to give it functions. These functions are then called by Absinthe.Plug automatically if they exist, and Absinthe.Plug passes in Plug.Conn which you can interact with. For example (quite rough):

      def update_conn(conn) do
          whatever_needs_doing
      end
      Map.update!(resolution, :after_resolve, fn actions ->
        actions ++ [update_conn]
      end)

I’m still quite new to Elixir so I haven’t made any use of macros but maybe the Map.update side of things (to update the resolution struct) could also be captured in a macro to make it a little less verbose.

woohaaha

woohaaha

Hi there :wave:

I have been trying to find more information about what you’re describing here.

Basically I would do a post to a login endpoint, get authenticated and then set a cookie, and then be redirected to a page that uses a client (Apollo maybe) to make GraphQL requests. “Then it’s just a matter of adding what authentication info you need into the absinthe resolution context”. (can you elaborate?)

This sounds like what I want but I’d like to see some examples. All blog posts/videos I have come across are using Guardian, or JWT. I’m looking for examples with hand rolled auth using a cookie. Can you elaborate on the two parts of this?

Part 1. Logging in without GraphQL, setting the session, and redirecting to an SPA/Client
Part 2. Ensuring all subsequent http requests are authenticated and authorized in the “resolution context”

Thank you for any guidance or direction (links to articles or github repos perhaps?) you can provide
:pray: :pray: :pray:

axelson

axelson

Scenic Core Team

Have you looked at the context and authentication guide in the docs? The Context and Authentication — absinthe v1.11.0
You create a plug and call Absinthe.Plug.put_options(conn, context: context) where context has something like: {current_user: user, login_count: 9} and whatever other info you need in your resolvers for your context.

I’d recommend looking into Pow - Robust, modular, extendable user authentication and management system for the authentication if you don’t currently have anything, but plain hand-rolled cookies can be fine too, although I don’t have any guides handy for that.

Edit: sent early

— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
psy-q
I’m trying to set up Emacs with elixir-ls via lsp-mode and credo via Flycheck. This should mostly be preconfigured as Flycheck picks up c...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews