1
votes

We use Guardian to generate tokens, which we then use for authentication when connecting to Phoenix Channels' socket.

Recently we found that some users never leave certain pages, and after a month or so, the token becomes invalid, which invalidates the Phoenix Channels connection attempt.

How do you handle such cases on the client-side? Is there a particular error that can be returned from Phoenix to let the front-end know what is the reason? Our connect function in user_socket.ex looks like so:

def connect(%{"guardian_token" => token}, socket) do
  case Guardian.Phoenix.Socket.authenticate(socket, MyApp.Guardian, token) do
    {:ok, authed_socket} ->
      {:ok, authed_socket}

    {:error, _} ->
      :error
  end
end

Is there a way to capture this error using the Phoenix JS library for Phoenix Channels? Our goal is to 1) stop it from retrying if the token is expired, 2) potentially log out the user or show a message that the user is offline. We checked the Phoenix JS' documentation but couldn't find anything suitable.

2
This is a very old question, but there still does not seem to be a way from the client to know whether the "error" was because the connection was dropped (maybe when re-deploying the app) or from an authentication error like an invalid token.Ross Allen

2 Answers

0
votes

You can try to refresh the tokens with each connect or whenever you see fit

Maybe something like this

# Refresh a token and set it on connect
def connect(%{"guardian_token" => token}, socket) do

  case MyApp.Guardian.refresh(token) do

    {:ok, _old_stuff, {new_token, new_claims}} -> 
      case Guardian.Phoenix.Socket.authenticate(socket, MyApp.Guardian, new_token, new_claims) do
        {:ok, authed_socket} ->
          {:ok, authed_socket}
        {:error, _} ->
          :socket_auth_failed
      end

    _ ->
      {:token_refresh_failed, "could not refresh token"}
  end
end

0
votes

This can be handled on the client-side.

Here is some pseudocode that demonstrates an auto-disconnect flow:

// Assuming you are adding the token (JWT) to the socket URL's query parameters
const socket = new Socket(url, { params: { jwt }});
socket.connect();

// Listen to socket errors
socket.onError(error => {
  if (!isValidJwt(jwt)) {
    console.log("JWT is no longer valid. Disconnecting.");
    socket.disconnect();
  }
})

function isValidJwt(jwt) {
  // Provide your own validation logic here
  // For JWTs, you can parse the JWT, read the expiration time,
  // and compare it with the current time.
}

Additionally, you can locally test this by making temporary modifications to the server-side code.

At the place where you generate an auth token, give the token a small time-to-live (TTL):

// Temporary change, for testing
conn = MyApp.Guardian.Plug.sign_in(conn, user, %{}, ttl: {10, :second})

This makes the JWT expire after 10 seconds.

With the modified server-side change, have the client log in and connect to the socket. Kill the Phoenix server to trigger the socket object's auto-reconnect mechanism. While the socket errors trying to reconnect, your callback function will notice that the token is no longer valid, and it will disconnect the socket.