mauric

mauric

Absinthe authentication with JWT tokens

Hello you all!

I’m developing an app with Phoenix + Absinthe with React on the front-end. I’m using Guardian to generate the JWT token and with the login mutation I return the token to the front, then I save the token into a cookie. Using Apollo, I include that token into the header and on the server I wrote a Context to check the value and authenticate the user.

I want to improve this solution in terms of where to store the token or even use another solution.

The solution is to store the token on a httpOnly cookie set up by the server ? How to do that ?

Thanks!

Marked As Solved

amcvitty

amcvitty

I don’t know if you actually got a good answer here. Our setup is very similar React → Absinthe/Phoenix.

We set an httpOnly cookie like this and as someone else noted, it gets sent with the HTTP requests.

router.ex

  scope "/api/graphql" do
    pipe_through [
      :graphql,
      :api_version,
      :api_auth,
      :set_sentry_context,
      :inject_graphql_context
    ]

    forward(
      "/",
      Absinthe.Plug,
      schema: OurAppWeb.Graphql.Schema,
      analyze_complexity: true,
      max_complexity: 1000,
      pipeline: {OurAppWeb.Grapqhl.Schema.Pipeline, :pipeline},
      before_send: {OurAppWeb.Authentication.AbsintheCookieResponse, :absinthe_before_send}
    )
  end

absinthe_cookie_response.ex

defmodule OurAppWeb.Authentication.AbsintheCookieResponse do
  # Used by router like https://hexdocs.pm/absinthe_plug/Absinthe.Plug.html#module-before-send
  def absinthe_before_send(conn, %Absinthe.Blueprint{} = blueprint) do
    if Map.has_key?(blueprint.execution.context, :access_token) do
      access_token = blueprint.execution.context.access_token

      Plug.Conn.put_resp_cookie(
        conn,
        # This name matches what is expected by Guardian.Plug.VerifyCookie
        "guardian_default_token",
        access_token || "",
        # Setting expires in the past is the official way to delete a cookie
        # https://stackoverflow.com/a/53573622
        max_age: if(access_token, do: 25 * 365 * 24 * 60 * 60, else: -100_000),
        http_only: true,
        secure: Application.get_env(:our_app, :cookie_secure)
      )
    else
      conn
    end
  end

  def absinthe_before_send(conn, _) do
    conn
  end
end

in schema somewhere:

  object :user_mutations do
    field :login_with_password, type: :login_with_password_payload do
      arg :input, non_null(:login_with_password_input)

      resolve &UserResolver.login_with_password/2

      # Put the user and token in the context 
      middleware fn res, _ ->
        with %{value: %{user: user, token: token}} <- res do
          next_context =
            res.context
            |> Map.put(:current_user, user)
            |> Map.put(:access_token, token)

          %{res | context: next_context}
        end
      end
    end

We have a different token for websockets for subscriptions, which gets created by a simple graphql HTTP call, then passed on the websocket creation.

Also Liked

autodidaddict

autodidaddict

Author of Real World Event Sourcing

The following may not be applicable because I don’t know all the context for your use case… but one thing that’s worth remembering about signed JWTs is that they are self-validating. If you trust the signer of the token, then you can trust the token. This means that your Absinthe API doesn’t need to generate tokens at all (or keep them in sessions). It can simply accept a bearer token in the Authorization header of all requests and then you crack that open when you build your context and add a user, scopes, etc.

This is essentially what I’m doing for my project. I don’t know if there are hidden dangers here, but I like this because it means that the front end can generate and sign tokens at will and the back-end can validate those tokens, and nobody needs a session anywhere.

autodidaddict

autodidaddict

Author of Real World Event Sourcing

If you sign a JWT with a private key, and include the corresponding public key inside the JWT in the iss field, then you know that the JWT was signed by whoever claims to have signed it (assuming you’re validating the signature). You can essentially use the issuer field like an API key - if you trust the issuer (e.g. your web UI) then you can simply accept the contents at face value and assume that the user (sub) in the JWT is correct. You can use aud to validate that the token is going to a specific URL and you can use the expiration field to make time-limited bearer tokens, etc.

This is why I said I don’t know if what I’m suggesting is appropriate for your use case - some scenarios don’t allow for this kind of security pattern.

autodidaddict

autodidaddict

Author of Real World Event Sourcing

You’re quite right. Calling them self validating without providing the context of using ed25519 keys to sign the tokens didn’t give enough context. My apologies.

Where Next?

Popular in Questions Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

Other popular topics Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement