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

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New
dillonoconnor
Is there any way to avoid the Hologram compiler running when using iex? It seems like the front-end code could potentially be disregarded...
New
thiagogsr
** (ArgumentError) expected :max_attempts to be a positive integer, got: {:@, [line: 10, column: 19], [{:max_attempts, [line: 10, column:...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
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
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
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

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews