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

New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
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
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
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
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 43622 214
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a &gt; b) do {:ok, "a"} end if (a &lt; b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36128 110
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New

We're in Beta

About us Mission Statement