jarvism

jarvism

Get Token for Protected API Routes via API (not UI)

Hello!
I have a Phoenix 1.7 LiveView app up and running with auth via
mix phx.gen.auth Accounts User users.
I’m still learning, but really enjoying it so far!
I’m running into a situation, though, where I am not quite sure even how to ask. Basically, I want to allow API users (outside of the LiveView) to access protected routes.
If I try to POST to /users/log_in, I get CSRF errors, which I think is correct. So, what is the correct way to get a token to use with the API?
Should I

  1. use mix phx.gen.secret to
  2. add an API key table with the user ID as a foreign key
  3. build my own plug to check the API key
  4. use Phoenix.Token to return a token if the API key is in the table?

I feel like I’m probably missing something since this must be a common use case with a canonical implementation.
Please point me in the right direction.
Thank you!

Most Liked

mindok

mindok

I’m not that qualified in this area, and our authentication system was built on the back of Ash Authentication rather than using phx.gen.auth. However, the following may give you some pointers:

  1. In your router you need an API pipeline that bypasses the CSRF checks. A default Phoenix project should already have an :api pipeline.

  2. You would then expose a controller through this API pipeline to retrieve a token, e.g.

  scope "/api", MyWeb do
    pipe_through [:api]

    post "/get_token", AuthApiController, :get_token
  end

This wouldn’t include the controllers that expose functionality you want protected behind auth.

  1. Update your :api pipeline to attempt authentication, e.g. by reading a bearer token from the HTTP headers, checking that token, if successful, setting a :current_user assign on the connection. I’m not sure how you do that with phx.gen.auth. Ash Authentication has a load_from_bearer plug that handles most of the mechanics for us.

Our API pipeline looks more or less like:

  pipeline :api do
    plug :accepts, ["json"]
    plug :load_user_from_bearer_token # Try to load user from token
  end
  1. Add an auth check step to the protected routes, e.g.
  scope "/" do
    # The auth check is tacked onto the end of the pipeline
    pipe_through [:api, AuthApiPlug.api_user_required] 

    post "/sensitive_api/secret_thingz", SecretThingController, :create
  end

The AuthApiPlug.api_user_required function plug looks like this:

  def api_user_required(conn, _opts) do
    current_user = conn.assigns[:current_user]

    if is_struct(current_user, User) do
      conn
    else
      payload =
        %{
          errors: [
            %{
              message: "Access denied. Authentication required."
            }
          ]
        }
        |> Jason.encode!()

      conn
      |> put_resp_content_type("application/json")
      |> send_resp(401, payload)
      |> halt()
    end
  end

Hope this at least helps you know what to ask!!

arcanemachine

arcanemachine

I have a project that contains an implementation of what you’re describing:

https://github.com/arcanemachine/phoenix-todo-list

In short, you use the :api pipeline in your router instead of the :browser pipeline so you can bypass the CSRF stuff. Then, you make 2 custom plugs:

  1. A plug to fetch the current API user (based on the bearer token that they pass in the Authorization HTTP header)

  2. A plug or two to fetch the desired resource, and to verify that the user is eligible to access the protected resource.

It’s been a little while since I made this (like a couple months), but I believe I had to implement the bearer token auth myself. It uses the exact same auth token generation scheme as that used by the built-in auth generators, but repackages the values so that they can be delivered over JSON. I dunno, it’s all in the repo, and I documented and tested everything pretty well (no warranties but I’m quite certain I didn’t do anything stupid.).

There’s also an OpenAPI spec and client for it in on the live site for the project (it’s just a todo list CRUD API), so if you’re familiar with those, you can use the online client or import the spec into Postman or Insomnia or whatever. There’s also an Insomnia spec that I used for development which should work (you may need to tweak the environment variables).

It may not be a perfect implementation, but it should have the basics of what you’re looking for. Hope it helps.

EDIT: Looks like I posted pretty much everything that @mindok said :smiley:

codeanpeace

codeanpeace

It’s definitely worth reading through all the code generated by mix phx.gen.auth since it was designed to be very modular and extensible. For example, some of the functions that get added to your auth context could also be re-used in your API auth workflow and/or serve as a starting point for writing API auth-specific code.

Plugs are very much meant to be hand-rolled for encapsulating shared logic. If you haven’t already, check out this section of the Phoenix guides on Plugs as composition.

Where Next?

Popular in Questions Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
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
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: The documentation above suggests that while ...
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
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
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
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
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
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
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
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
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
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
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
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New

We're in Beta

About us Mission Statement