ueberauth / guardian

Elixir Authentication
MIT License
3.43k stars 382 forks source link

Following Secret Key examples with JOSE leads to an error #136

Closed ospaarmann closed 8 years ago

ospaarmann commented 8 years ago

When following the examples in the Readme.md for using JOSE.JWK to create / read in keys from a password protected file I always encounter the error

** (Mix.Config.LoadError) could not load config config/config.exs
    ** (UndefinedFunctionError) undefined function JOSE.JWK.from_file/2 (module JOSE.JWK is not available)

This is the code in my config.exs

# Configure Guardian for JWT Authentication
config :guardian, Guardian,
  allowed_algos: ["HS512"], # optional
  verify_module: Guardian.JWT,  # optional
  issuer: "MyApp",
  ttl: { 30, :days },
  verify_issuer: true, # optional
  secret_key:  System.get_env("GUARDIAN_KEY_PASSPHRASE") |> JOSE.JWK.from_file(System.get_env("GUARDIAN_KEY_FILE")),
  serializer: MyApp.GuardianSerializer
doomspork commented 8 years ago

I believe this has to do with how and when configuration files are evaluated and compiled. @ospaarmann can you try putting it in an anonymous function?

config :guardian, Guardian,
  ...
  secret_key:  fn -> System.get_env("GUARDIAN_KEY_PASSPHRASE") |> JOSE.JWK.from_file(System.get_env("GUARDIAN_KEY_FILE")) end,
  serializer: MyApp.GuardianSerializer
ospaarmann commented 8 years ago

This compiles. But now the config value is the function itself and not what it returns:

iex(2)> Guardian.config
[allowed_algos: ["HS512"], verify_module: Guardian.JWT, issuer: "MyApp",
 ttl: {30, :days}, verify_issuer: true,
 secret_key: #Function<20.50752066/0 in :erl_eval.expr/5>,
 serializer: MyApp.GuardianSerializer]
iex(3)> Guardian.config(:secret_key)
#Function<20.50752066/0 in :erl_eval.expr/5>
doomspork commented 8 years ago

@ospaarmann what about Guardian.config(:secret_key).()?

ospaarmann commented 8 years ago

This returns the correct value but I believe the Guardian configuration itself is still not correct. No?

doomspork commented 8 years ago

@ospaarmann JOSE accepts functions: https://github.com/ueberauth/guardian#secret-key.

Have you tried using the configuration?

ospaarmann commented 8 years ago

No. I have to check some other things since I'm not using Ecto but will report back. Thanks for the help

ospaarmann commented 8 years ago

I tried this in another Phoenix App that uses Ecto and is less custom-made. It does not work. The error I'm getting is

[error] #PID<0.379.0> running MyApp.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /api/v1/sessions
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in :base64url.encode/1
        src/base64url.erl:22: :base64url.encode(#Function<20.50752066/0 in :erl_eval.expr/5>)
        lib/guardian.ex:291: Guardian.jose_jwk/1
        lib/guardian.ex:298: Guardian.encode_claims/1
        lib/guardian.ex:89: Guardian.encode_and_sign/3
        (data_bucket) web/controllers/session_controller.ex:15: MyApp.SessionController.create/2
        (data_bucket) web/controllers/session_controller.ex:1: MyApp.SessionController.action/2
        (data_bucket) web/controllers/session_controller.ex:1: MyApp.SessionController.phoenix_controller_pipeline/2
        (data_bucket) lib/phoenix/router.ex:261: MyApp.Router.dispatch/2
        (data_bucket) web/router.ex:1: MyApp.Router.do_call/2
        (data_bucket) lib/data_bucket/endpoint.ex:1: MyApp.Endpoint.phoenix_pipeline/1
        (data_bucket) lib/plug/debugger.ex:93: MyApp.Endpoint."call (overridable 3)"/2
        (data_bucket) lib/phoenix/endpoint/render_errors.ex:34: MyApp.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

So encoding the claims with the JWK fails and there again we find the reference to the anonymous function.

ospaarmann commented 8 years ago

So I found the problem. The Readme claims that functions can be passed as Guardian.config(:secret_key) but the necessary changes are only present in the master branch and not in the latest release. They were introduced with this pull request.

I suggest to either create a release with these changes or mention this in the Readme to save other people some time :)

simonh1000 commented 8 years ago

Is the code up to date? Key generation is not at all clear in the docs, as the link to Examples: Key Generation has 8 different methods. Which one do I use?

ospaarmann commented 8 years ago

This is what I did to generate a password protected JWK file (which contains the generated key):

mix phoenix.gen.secret
touch .jwk_file

The first line just generates a secret I'm using as a password. And .jwk_file is the file to store the jwk in. You can name it whatever you want.

iex -S mix phoenix.server

iex(1)> jwk = JOSE.JWK.generate_key({:ec, :secp521r1})
iex(2)> password = "The Secret we just generated"
iex(3)> file = ".jwk_file"
iex(4)> JOSE.JWK.to_file(password, file, jwk)

Then in your config.exs

config :guardian, Guardian,
  allowed_algos: ["HS512"], # optional
  verify_module: Guardian.JWT,  # optional
  issuer: "MyApp",
  ttl: { 30, :days },
  verify_issuer: true, # optional
  secret_key: fn ->
    {jwe, jwk} = System.get_env("GUARDIAN_JWK_PASSPHRASE") |> JOSE.JWK.from_file(System.get_env("GUARDIAN_JWK_FILE"))
    jwk
  end,
  serializer: MyApp.GuardianSerializer

Of course you have to pass in your environment variables. So GUARDIAN_JWK_PASSPHRASE should be the password (secret generated earlier) and GUARDIAN_JWK_FILE should be '.jwk_file' in this example.

The pattern matching is used because JOSE.JWK.from_file/2 returns the jwk plus the standard jwe and we have to fish out the jwk. And we have to wrap it in an anonymous function because of the way the configuration is parsed (before everything else, so JOSE.JWK would not be available otherwise).

simonh1000 commented 8 years ago

Thanks, I've had to hard code the passphrase in as I don't know about env variables yet, but it seemed to compile

hassox commented 8 years ago

@ospaarmann The new secret key stuff in the readme is not yet released which is why you'll be having some trouble with it. I'll try and get a release cut soon to update the released guardian to match the readme.

hassox commented 8 years ago

Guardian 0.11.1 has been released with all the new JWT goodness.