Guardian Redis issue: [error] ** (FunctionClauseError) no function clause matching

I have been learning Elixir for the past 7 days and honestly I am in love with the language.

There has been one issue that is bugging me for straight 2 days and I can’t seem to fix it, and since I am new into Elixir I am not even trying to modify libraries myself.

Here is the issue, I am using guardian 2.3.2, guardian_db 3.0.0 and guardian_redis 0.2.0. Since when calling database with guardian_db everytime just to check validation of JWT is expensive, I decided to implement it with redis, when turns out there is library called guardian_redis. But here is the issue. Whenever I add it to my project and try to login I get this error:

[error] ** (FunctionClauseError) no function clause matching in someprojectBackendElixirWeb.Router.handle_errors/2
    (someproject_backend_elixir 0.1.0) lib/someproject_backend_elixir_web/router.ex:5: someprojectBackendElixirWeb.Router.handle_errors(%Plug.Conn{adapter: {Bandit.Adapter, :...}, assigns: %{}, body_params: %{"email" => "someemail@gmail.com", "hashed_password" => "password"}, cookies: %{"_someproject_backend_elixir_key" => "SFMyNTY.g3QAAAABbQAAAAphY2NvdW50X2lkbQAAACQyYWRlZTRlYy1jZTIyLTQ2MGUtYTJiZi00YzJmMmExMGU2MWU.dzBeNhacCXQwwTnAWqWSO01h0Wj2LN5GvusHC2AmkSU"}, halted: false, host: "localhost", method: "POST", owner: #PID<0.649.0>, params: %{"email" => "someemail@gmail.com", "hashed_password" => "password"}, path_info: ["api", "accounts", "login"], path_params: %{}, port: 4000, private: %{:phoenix_view => %{"html" => someprojectBackendElixirWeb.AccountHTML, "json" => someprojectBackendElixirWeb.AccountJSON}, someprojectBackendElixirWeb.Router => [], :phoenix_endpoint => someprojectBackendElixirWeb.Endpoint, :plug_session_fetch => :done, :plug_session => %{"account_id" => "2adee4ec-ce22-460e-a2bf-4c2f2a10e61e"}, :before_send => [#Function<0.9035112/1 in Plug.Session.before_send/2>, #Function<0.106864063/1 in Plug.Telemetry.call/2>], :phoenix_router => someprojectBackendElixirWeb.Router, :phoenix_action => :login, :phoenix_layout => %{"html" => {someprojectBackendElixirWeb.Layouts, :app}}, :phoenix_controller => someprojectBackendElixirWeb.AccountController, :phoenix_format => "json"}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %{"_someproject_backend_elixir_key" => "SFMyNTY.g3QAAAABbQAAAAphY2NvdW50X2lkbQAAACQyYWRlZTRlYy1jZTIyLTQ2MGUtYTJiZi00YzJmMmExMGU2MWU.dzBeNhacCXQwwTnAWqWSO01h0Wj2LN5GvusHC2AmkSU"}, req_headers: [{"cookie", "_someproject_backend_elixir_key=SFMyNTY.g3QAAAABbQAAAAphY2NvdW50X2lkbQAAACQyYWRlZTRlYy1jZTIyLTQ2MGUtYTJiZi00YzJmMmExMGU2MWU.dzBeNhacCXQwwTnAWqWSO01h0Wj2LN5GvusHC2AmkSU"}, {"content-length", "84"}, {"connection", "keep-alive"}, {"accept-encoding", "gzip, deflate, br"}, {"host", "localhost:4000"}, {"postman-token", "6939017f-2db8-4b02-9d43-aa4f6ab93a12"}, {"accept", "*/*"}, {"user-agent", "PostmanRuntime/7.41.2"}, {"content-type", "application/json"}], request_path: "/api/accounts/login", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "F_HQOB_eYIC1Qd8AAACN"}], scheme: :http, script_name: [], secret_key_base: :..., state: :unset, status: 500}, %{reason: %MatchError{term: {:error, :token_storage_failure}}, stack: [{someprojectBackendElixirWeb.Auth.Guardian, :create_token, 1, [file: ~c"lib/someproject_backend_elixir_web/auth/guardian.ex", line: 41]}, {someprojectBackendElixirWeb.AccountController, :login, 2, [file: ~c"lib/someproject_backend_elixir_web/controllers/account_controller.ex", line: 37]}, {someprojectBackendElixirWeb.AccountController, :action, 2, [file: ~c"lib/someproject_backend_elixir_web/controllers/account_controller.ex", line: 1]}, {someprojectBackendElixirWeb.AccountController, :phoenix_controller_pipeline, 2, [file: ~c"lib/someproject_backend_elixir_web/controllers/account_controller.ex", line: 1]}, {Phoenix.Router, :__call__, 5, [file: ~c"lib/phoenix/router.ex", line: 484]}, {someprojectBackendElixirWeb.Router, :call, 2, [file: ~c"deps/plug/lib/plug/error_handler.ex", line: 80]}, {someprojectBackendElixirWeb.Endpoint, :plug_builder_call, 2, [file: ~c"lib/someproject_backend_elixir_web/endpoint.ex", line: 1]}, {someprojectBackendElixirWeb.Endpoint, :"call (overridable 3)", 2, [file: ~c"deps/plug/lib/plug/debugger.ex", line: 136]}, {someprojectBackendElixirWeb.Endpoint, :call, 2, [file: ~c"lib/someproject_backend_elixir_web/endpoint.ex", line: 1]}, {Phoenix.Endpoint.SyncCodeReloadPlug, :do_call, 4, [file: ~c"lib/phoenix/endpoint/sync_code_reload_plug.ex", line: 22]}, {Bandit.Pipeline, :call_plug!, 2, [file: ~c"lib/bandit/pipeline.ex", line: 124]}, {Bandit.Pipeline, :run, 4, [file: ~c"lib/bandit/pipeline.ex", line: 36]}, {Bandit.HTTP1.Handler, :handle_data, 3, [file: ~c"lib/bandit/http1/handler.ex", line: 12]}, {Bandit.DelegatingHandler, :handle_data, 3, [file: ~c"lib/bandit/delegating_handler.ex", line: 18]}, {Bandit.DelegatingHandler, :handle_continue, 2, [file: ~c"d:/Stvari/Programiranje/someprojectFinal/someproject_backend_elixir/deps/thousand_island/lib/thousand_island/handler.ex", line: 411]}, {:gen_server, :try_handle_continue, 3, [file: ~c"gen_server.erl", line: 2163]}, {:gen_server, :loop, 7, [file: ~c"gen_server.erl", line: 2072]}, {:proc_lib, :init_p_do_apply, 3, [file: ~c"proc_lib.erl", line: 329]}], kind: :error})
    (plug 1.16.1) lib/plug/error_handler.ex:113: Plug.ErrorHandler.__catch__/6
    (someproject_backend_elixir 0.1.0) lib/someproject_backend_elixir_web/endpoint.ex:1: someprojectBackendElixirWeb.Endpoint.plug_builder_call/2
    (someproject_backend_elixir 0.1.0) deps/plug/lib/plug/debugger.ex:136: someprojectBackendElixirWeb.Endpoint."call (overridable 3)"/2
    (someproject_backend_elixir 0.1.0) lib/someproject_backend_elixir_web/endpoint.ex:1: someprojectBackendElixirWeb.Endpoint.call/2
    (phoenix 1.7.14) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (bandit 1.5.7) lib/bandit/pipeline.ex:124: Bandit.Pipeline.call_plug!/2
    (bandit 1.5.7) lib/bandit/pipeline.ex:36: Bandit.Pipeline.run/4
    (bandit 1.5.7) lib/bandit/http1/handler.ex:12: Bandit.HTTP1.Handler.handle_data/3     
    (bandit 1.5.7) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
    (bandit 1.5.7) d:/Stvari/Programiranje/someprojectFinal/someproject_backend_elixir/deps/thousand_island/lib/thousand_island/handler.ex:411: Bandit.DelegatingHandler.handle_continue/2
    (stdlib 6.0.1) gen_server.erl:2163: :gen_server.try_handle_continue/3
    (stdlib 6.0.1) gen_server.erl:2072: :gen_server.loop/7
    (stdlib 6.0.1) proc_lib.erl:329: :proc_lib.init_p_do_apply/3

Now I don’t really understand why is this happening when I have been following guardian_redis docs and put everything in right place that I need.

In config.exs:

config :guardian, Guardian.DB,
  adapter: GuardianRedis.Adapter

config :guardian_redis, :redis,
  host: "my upstash redis url is here",
  port: 6379,
  password: "my upstash redis password is here",
  pool_size: 10

And in my application.ex under children I added:

GuardianRedis.Redix

Also it is said that I should first set up Guardian.DB normally like how I would do it and just change whtat they say which I also did and these functions exist as well:

defmodule SomeprojectBackendElixirWeb.Auth.Guardian do
    use Guardian, otp_app: :someproject_backend_elixir
    alias SomeprojectBackendElixir.Accounts
  
    def subject_for_token(%{id: id}, _claims) do
        sub = to_string(id)
        {:ok, sub}
    end
  
    def subject_for_token(_, _) do
        {:error, :no_id_provided}
    end
  
    def resource_from_claims(%{"sub" => id}) do
        case Accounts.get_account!(id) do
            nil -> {:error, :not_found}
            resource -> {:ok, resource}
        end
    end
  
    def resource_from_claims(_claims) do
        {:error, :no_id_provided}
    end
  
    def authenticate(email, password) do
        case Accounts.get_account_by_email(email) do
            nil -> {:error, :unauthored}
            account ->
            case validate_password(password, account.hashed_password) do
                true -> create_token(account)
                false -> {:error, :unauthorized}
            end
        end
    end
  
    defp validate_password(password, hashed_password) do
        Bcrypt.verify_pass(password, hashed_password)
    end
  
    defp create_token(account) do
        {:ok, token, _claims} = encode_and_sign(account)
        {:ok, account, token}
    end
  
    def after_encode_and_sign(resource, claims, token, _options) do
        with {:ok, _} <- Guardian.DB.after_encode_and_sign(resource, claims["typ"], claims, token) do
            {:ok, token}
        end
    end
  
    def on_verify(claims, token, _options) do
        with {:ok, _} <- Guardian.DB.on_verify(claims, token) do
            {:ok, claims}
        end
    end
  
    def on_refresh({old_token, old_claims}, {new_token, new_claims}, _options) do
        with {:ok, _, _} <- Guardian.DB.on_refresh({old_token, old_claims}, {new_token, new_claims}) do
            {:ok, {old_token, old_claims}, {new_token, new_claims}}
        end
    end
  
    def on_revoke(claims, token, _options) do
        with {:ok, _} <- Guardian.DB.on_revoke(claims, token) do
            {:ok, claims}
        end
    end
end

These are all relevant code blocks, so as you see per documentations everything is done properly but cant seem to fix the error I am getting only when having guardian_redis package and including it.

Thank you in advance :slight_smile:

The error you post comes from a mismatch in the handle_error/2 function in your router and obscures your real error, which is {:error, :token_storage_failure}.

I have never used guardian and certainly not with Redis.

Unless you expect several million authentications per second, I would start with just using Postgres, that is what we all do with all kinds of other authentication mechanisms. Just my 2c.

1 Like

Ah yes silly me. I fixed the error and I clearly got response that storing token was not working. So I tested Redis on localhost and it turns out it works perfectly and that actual error was connection with Upstash Redis.

Thank you a lot for giving me insight!

Have a good day :slight_smile: !

1 Like