ueberauth / guardian

Elixir Authentication
MIT License
3.44k stars 381 forks source link

%FunctionClauseError{arity: 1, function: :jose_jwk, module: Guardian} #291

Closed psteininger closed 7 years ago

psteininger commented 7 years ago

Hi, I am trying to get Guardian working with RS256 tokens issued by Auth0. With Auth0 and RS256 algo, the tokens are signed with a private key related to a certificate for the Account, and then verified with the public key inside the cert. I extracted the public key in PEM format and stored it in ENV var. This is exactly the setup that works for another app built with Ruby + Grape. In development env, I use envy to load env vars from .env. My guardian setup is as follows:

config :guardian, Guardian,
  allowed_algos: ["RS256"], # optional
  verify_module: Guardian.JWT,  # optional
  issuer: System.get_env("AUTH0_DOMAIN"),
  ttl: { 30, :days },
  allowed_drift: 2000,
  verify_issuer: false,
  secret_key: fn ->
    pem = System.get_env("AUTH0_PUBLIC_KEY") |> Kernel.||("") |> String.replace("\\n", "\n") |> JOSE.JWK.from_pem
    pem
  end

What I do for the secret is I load it from a PEM encoded string stored in AUTH0_PUBLIC_KEY env var. I have to wrap it in fn, because the first time around AUTH0_PUBLIC_KEY comes out empty. I did successfully verify a token like this:

jwt = "......."
secret = System.get_env("AUTH0_PUBLIC_KEY") |> Kernel.||("") |> String.replace("\\n", "\n") |> JOSE.JWK.from_pem  
JOSE.JWT.verify  secret, jwt  

end got

{true,                                                                                                                          
 %JOSE.JWT{fields: %{"aud" => "https://________.laborvoices.com",
    "exp" => 1493321100, "iat" => 1493234700,
    "iss" => "https://laborvoices.auth0.com/", "scope" => "",
    "sub" => "___________@clients"}},
 %JOSE.JWS{alg: {:jose_jws_alg_rsa_pkcs1_v1_5, :RS256}, b64: :undefined,
  fields: %{"kid" => "_____________",
    "typ" => "JWT"}}}

____ are necessary redactions

Could someone point me in the right direction? I am just starting out with Elixir and Phoenix (after years of Ruby/Rails, Java, PHP, and other stuff).

Thanks in advance

psteininger commented 7 years ago

I got my setup to work in a round-about way, which is suggested in: https://github.com/ueberauth/guardian/issues/187

#/my_app/lib/myapp.ex
def start(_type, _args) do
    unless Mix.env == :prod do
      Envy.auto_load
      Mix.Config.read!("config/config.exs") |> Mix.Config.persist

    end
    guardian_env = Application.get_env(:guardian, Guardian)
    pem = System.get_env("AUTH0_PUBLIC_KEY") |> Kernel.||("") |> String.replace("\\n", "\n") |> JOSE.JWK.from_pem
    new_guardian_env = guardian_env |> Keyword.put(:secret_key, pem)
    Application.put_env(:guardian, Guardian, new_guardian_env)

    import Supervisor.Spec
...
end

I hope this helps anyone trying to get things working with envy and auth0. I will try to blog about it next week and add a link here

psteininger commented 7 years ago

I also wanted to add a note that with Auth0 the certificate for each account is publicly accessible, so committing the whole cert or even just the extracted public key would be quite OK.

scrogson commented 7 years ago

Tackling something like this will be much easier with v1.0.

Something like this should do the trick:

config :my_app, MyApp.Guardian.Auth0,
  # ...
  secret_key: {MyApp.Guardian.Auth0, :fetch_secret, []}
defmodule MyApp.Guardian.Auth0 do
  use Guardian, otp_app: :my_app

  def fetch_secret do
    System.get_env("AUTH0_PUBLIC_KEY")
    |> Kernel.||("")
    |> String.replace("\\n", "\n")
    |> JOSE.JWK.from_pem()
  end
end
psteininger commented 7 years ago

great to know @scrogson.

BTW, as I mentioned earlier. For Auth0 specifically, using RS256 algorithm, one can just use a certificate from file (in PEM format), and JOSE automatically extracts and uses the public key. So if could be even better:


def fetch_secret do
   JOSE.JWK.from_pem_file("hard/coded/path/to/cert.pem")
end
scrogson commented 7 years ago

Excellent!