Guardian giving invalid_token error, react problem?

Just looking for clarity here I think. Working through a React/Phoenix tutorial, but he is using old-style Guardian and much seems to have changed for v1.0. I think I have everything set up correctly, but I am getting an invalid-token error:

MyApp.Guardian.AuthErrorHandler.auth_error(
  %Plug.Conn{…},
  {:invalid_token, %CaseClauseError{term: {:error, {:badarg, ["null"]}}}},
  [realm_reg: ~r/Bearer:? +(.*)$/i, realm: "Bearer"]
)

and I suspect that it is caused by the {"authorization", "Bearer: null"} entry in the request header:

...
req_headers: [
  {"host", "localhost:4000"},
  {"connection", "keep-alive"},
  {"content-length", "11"},
  {"accept", "application/json"},
  {"origin", "http://localhost:3000"},
  {"user-agent", "blah blah"},
  {"authorization", "Bearer: null"},
  {"content-type", "application/json"},
  {"referer", "http://localhost:3000/signup"},
  {"accept-encoding", "gzip, deflate, br"},
  {"accept-language", "en-US,en;q=0.8,it-IT;q=0.6,it;q=0.4,fr-FR;q=0.2,fr;q=0.2"}
],
request_path: "/api/users"
...

So I think this is a React problem, but I just want to confirm that before I start chasing it around the wrong room. I’m still new enough at both things that it’s all very much slow learning at this point.

For the curious, this is the tutorial.

From the error it would seem it is a badarg (bad argument) error with the argument “null” being given. So I would say your assessment is correct and there is a problem with sending null from the frontend.

I do something like this in my js file

  socket = new Socket('/socket', {
    params: { token: AuthService.loadToken() },
    logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data); },
  });

My AuthService just load token from localstorage.

But I would like to point out a few things…

The post does not cover phoenix 1.3, but 1.2
The guardian example is almost the same as the guardian one

@OvermindDL1 would say not to use guardian, but Phoenix Token for this use case

1 Like

I think I get the problem now (could be wrong, of course), and it seems like it is a Guardian/Phoenix issue. On new user sign up there is no token for the frontend to get, so there is nothing to send in the Authorization Bearer field. So what do you do for sign ups? Take the POST resource out of the path of the Guardian plug in the router?

When I sign up, I do

axios.post(${ROOT_URL}/registrations, {user: params})

… which is a promise that returns a token …

  .then(response => {
    // - Save JWT token
    let {jwt, user} = response.data;
    AuthService.saveToken(jwt);
  })

… from my sign_up api controller.

  def create(conn, %{"user" => user_params}) do
    changeset = User.registration_changeset(%User{}, user_params)

    case Repo.insert(changeset) do
      {:ok, user} ->
        {:ok, jwt, _full_claims} = Guardian.encode_and_sign(user, :token)

        conn
        |> put_status(:created)
        |> render(SessionView, "show.json", jwt: jwt, user: user)

      {:error, changeset} ->
        conn
        |> put_status(:unprocessable_entity)
        |> render(RegistrationView, "error.json", changeset: changeset)
    end
  end

Hmmm, that’s pretty much the same thing I have, but clearly when you send the POST via

axios.post(${ROOT_URL}/registrations, {user: params})

there is no token yet (since the guardian hasn’t made one yet), so that cannot be in the headers, right? Do your headers contain something other than null? Any chance I can see what your router pipeline looks like? since that is where I am getting stopped.

My logic looks like this for my posting functionality:

function headers() {
  const token = JSON.parse(localStorage.getItem('token'));
  return {
    Accept: 'application/json',
    'Content-Type': 'application/json',
    Authorization: `Bearer: ${token}`
  };
}
...
async post(url, data) {
    const body = JSON.stringify(data);
    return parseResponse(
      await fetch(
        `${API}${url}`, {
          method: 'POST',
          headers: headers(),
          body
        }
      )
    );
  },

which I think looks very similar to what I would get from axios, and like this for my signup function:

function setCurrentUser(dispatch, response) {
  localStorage.setItem('token', JSON.stringify(response.meta.token));
  dispatch({ type: 'AUTHENTICATION_SUCCESS', response });
}

export function signup(data, router) {
  return async (dispatch) => {
    setCurrentUser(dispatch, await api.post('/users', { user: data }));
    dispatch(reset('signup'));
    router.transitionTo('/');
  };
}

Of course, the setCurrentUser function of the latter never gets called because Phoenix returns an error instead of a successful response with a token, breaking the promise that it is awaiting, but I thought I’d include it for completeness. Oh, and here is the error on the frontend side:

Uncaught (in promise) {message: "invalid_token"}

so we see that the await is never fulfilled.

It is easy to allow some routes without token in the router

  scope "/api", BlahWeb do
    pipe_through :api
    
    scope "/v1" do
      post "/registrations", RegistrationController, :create
      post "/sessions", SessionController, :create
      
      pipe_through :api_auth
      # authenticated
    end
  end