Giter Club home page Giter Club logo

joken's People

Contributors

ajkeys avatar alanpeabody avatar alecostard avatar bryanjos avatar cconstantin avatar chulkilee avatar cs-victor-nascimento avatar dependabot-preview[bot] avatar dependabot[bot] avatar dolfinus avatar evax avatar iamfromspace avatar ideamarcos avatar jhosteny avatar jsmestad avatar keichan34 avatar kianmeng avatar lbatson avatar llxff avatar lnikkila avatar maartenvanvliet avatar marpo60 avatar maxbeizer avatar mobileoverlord avatar msch avatar potatosalad avatar pragtob avatar tarang avatar van-mronov avatar victorolinasc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

joken's Issues

Allow the config_module key to be placed on any config block

Since there is now only one config item for Joken, config_module, it would probably be nice to make it so that the user can define which config block contains the key.

I propose change the config_module key to joken_module, and adding a using macro that takes an otp_app option which defines which config block to look for the joken_module key.

Something like below:

    #the config block
     config :my_app,
         joken_config: My.Config.Module

  #Next, tell Joken where to find the config block
    use Joken, otp_app: :my_app


  #then to encode and decode
      {:ok, token} = encode_token(%{username: "johndoe"})
      {:ok, decoded_payload} = decode_token(jwt)

I made a branch with the change and the only thing I could come up with is having the functions to encode and decode added to the using macro and called directly in the module using it. Since a lot of libraries use encode and decode as function names, in this branch I renamed the functions encode_token and decode_token.

Handling verification options

With the new API we should figure out what to do with "skip_claims" and "skip_verifying". I think the new API handles skipping claim validations when needed, but the skip_verifying option needs a bit more thought.

Add custom JOSE header arguments

I need to set custom header arguments to a JWT that I'm creating - what's the best way to do that using Joken - or should I be looking for a different elixir library?

Thanks!

Making the API better

I would like to make this library the best it can be. I think the current API reflects my misunderstandings of the JWT spec and how people use JWT in practice. I am making this issue to propose a better API and also to take comments and proposals for making Joken better as well.

I propose simplifying the library down to the essential functionality. What I feel is essential functionality is encoding to create a JWT, decoding a JWT, and verifying a JWT before decoding it. This does not cover validation of the claims at all. The reason for excluding the validation of the claims is because there are many ways to validate the ones even in the spec and because it appears that not too many people use the ones in the spec in practice. Even the current implementation of the library leaves it up to users to provide the validation themselves.

Note: The recent inclusion of the Plug to this library would remain here as well.

I can see an interface something like below

Joken.encode(payload, algorithm, secret, encode_fn) :: binary | {:error, binary}

payload would be a Dict that contains the data to be encoded.
algorithm would be any of the supported algorithms.
secret would be the binary or key used for encoding
encode_fn would be a user defined function to do json encoding

Joken.decode(token, algorithm, secret, decode_fn) :: Dict.t | {:error, binary}

token would be a binary token
algorithm would be the algorithm used to encode the token
secret would be the binary or key
decode_fn would be a user defined function to do json decoding

Joken.decode would also take care of verifying the signature of the token.

This would also remove the need for anything in configuration. This would probably benefit libraries that use Joken as well as it allows them for flexibility.

As stated, this is just a proposal. Would love to hear any feedback, thoughts, or alternative solutions. Also as stated, the Plug that was recently introduced would stay as well. This is really only for the actual encoding and decoding of tokens. @cs-victor-nascimento I would love to hear your thoughts on this.

Update for the README Joken.Config example implementation to not use atoms

I was using the hassox/guardian authentication framework, when I discovered a problem with their Joken.Config implementation (Guardian.JWT) which used the Poison library in a not recommended way:

ueberauth/guardian#13

in the joken README there is a Joken.Config example implementation where atoms are also used as keys:

    def decode(binary) do
      Poison.decode!(binary, keys: :atoms!)
    end

which can lead to a lot of pain:

https://github.com/devinus/poison#parser

I suggest to change the documentation to prevent wrong usage

thanks a lot for this library!

Defining the JSON module

Can somebody explain how to define the JSON module to get rid of this error during verification:

[warn] Error: %ArgumentError{message: "No JSON module defined"}

This is how I've defined my router:

import Joken
alias Joken.Token

pipeline :api do
  plug :accepts, ["json"]
  plug Joken.Plug, verify: &My.Router.verify_function/0
  plug :dispatch
end

def verify_function() do
  %Joken.Token{}
  |> Joken.with_signer(hs256("my_secret"))
end

I've tried this with the mix config.exs, but to no avail:

config :joken,
  json_module: Poison

Am I missing something obvious?

Using Joken.Plug with Phoenix causes a compile-time error

Just tried to upgrade from plug_jwt / joken 0.15.0 to joken 0.16.0 using the upgrade guides here, and now I can't get my Phoenix apps to compile.

The error returned is:

== Compilation error on file web/router.ex ==
** (ArgumentError) cannot escape #Function<0.82266480/2 in Joken.Plug.init/1>. The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, pids and remote functions in the format &Mod.fun/arity
    (elixir) src/elixir_quote.erl:302: :elixir_quote.bad_escape/1
    (elixir) src/elixir_quote.erl:246: :elixir_quote.do_quote/3
    (elixir) src/elixir_quote.erl:141: :elixir_quote.escape/2
    (elixir) lib/macro.ex:307: Macro.escape/2
    (plug) lib/plug/builder.ex:240: Plug.Builder.quote_plug_call/3
    (plug) lib/plug/builder.ex:215: Plug.Builder.quote_plug/4
    (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
    (plug) lib/plug/builder.ex:186: Plug.Builder.compile/3
    web/router.ex:12: (module)
    (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6
    (elixir) lib/kernel/parallel_compiler.ex:97: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8

Unsure if this is a compatibility problem with Joken/Phoenix, or if I've just missed something obvious!

I've managed to replicate the issue in a vanilla Phoenix app running Elixir 1.0.5 and 1.1.1

Allow optional messages for failed validations for better debugging/scenario handling

If a token has multiple validations, it is difficult to know which validation has failed and how to respond. Custom messages could be included with each validation that could be pattern matched against so the proper action can be performed.

In this example:

my_verified_token = compact_token
  |> token
  |> with_validation("exp", &(&1 > :os.system_time(:seconds)))
  |> with_validation("admin", &(&1 == true))
  |> with_signer(hs256("temp_secret"))
  |> verify!

If the token does not pass its validation, I get an "Invalid payload" error, but this doesn't tell me whether I should have them login again (401 and with a redirect to login), or tell them they don't have the privileges to access the resource (403).

If I want to respond to each individual case, I am restricted to a single validation, then to handle the claims in the controller.

My proposal is as follows: add an optional third parameter to with_validation which describes the error, should validation fail. The first failed validation populates the returned error key, and all validation errors are added to an errors list.

No breaking change, but added control.

I'm working on the code now and will open a PR soon; I can change the approach if there are any concerns.

claim error on compilation

Keep getting an error when I run mix phoenix.server or phoenix.digest
Elixir 1.1
Phoenix 1.0.3

lib/joken/claims.ex:23: warning: undefined protocol function to_claims/1 (for protocol Joken.Claims)

Benchmark suite

Since Joken will probably be a hot path in any APIs in the wild that use it, we need a performance benchmark suite to ensure future changes won't introduce performance regressions.

Add Plug features

Many people use plug_jwt or other frameworks that implement JWT signing and verification for Plug. This could be handled directly by Joken. Features that come to mind are:

  • A Plug: a plug for verifying if a request has a valid token. This should be configurable per route (using Plugs 0.14.0 ability to assign private options to a connection) with:
    • skipping the route altogether
    • passing specific options to route
    • include some function to operate on the payload
  • A Plug function to generate and add the token to a response

An example usage would be:

defmodule MyRouter do
  use Plug.Router

  @skip_auth %{joken_skip: true}
  @verify_admin %{joken_evaluate: fn(payload) -> payload.role == :admin end}

  plug :match
  plug Joken.Plug, joken_config: MyJWTConfig
  plug :dispatch

  # This will allow requests without an authentication header
  get "/", private: @skip_auth do
    # do stuff
  end

  # this will validate the token and check if the payload has a property role with value :admin
  post "/user", private: @verify_admin do
    # do stuff 
    # generates the token using the config and puts it in response's header
    conn |> Joken.Plug.generate(%{})
    # do stuff
  end

  match _, private: @skip_auth do
    # do stuff
  end
end

Example to use with RS256 for Google Cloud Platform

Hello, I'm trying to use Joken to get access tokens for Google Cloud Platform. I can't seem to use RS256 (which is what the docs say must be used). Could you please help me with this, maybe by providing an example?


    my_token = %{iss: "serviceEmail", 
                 scope: "theScope",
                 aud: "https://www.googleapis.com/oauth2/v4/token",
                 exp: 1457116200,
                 iat: 1457114100
                }
               |> token
               |> with_signer(rs256("my_secret"))

Small typo in README

In the config section of the README, algorithm is spelled wrong -- 'algorithem'.

:crypto.sha256_mac has new accessor?

Hi,
After seeing this error, I fired up iex and tried to invoke :crypto.sha256 with no luck. I'm running OTP 17.5 with elixir 1.0.4 on top of that.

:crypto.hash :sha256, "Hello" works, however. I'm not too familiar with Joken or Erlang's crypto library, but is this an issue with the function accessor changing? Please have a look at the error. I've removed our token string.

** (exit) an exception was raised:
    ** (ArgumentError) argument error
        (crypto) :crypto.sha256_mac_nif(nil, "<<< token string >>> ", 32)
        (crypto) crypto.erl:1029: :crypto.sha256_mac/3
        (joken) lib/joken/token.ex:27: Joken.Token.encode/5
        (phoenix_token_auth) lib/phoenix_token_auth/authenticator.ex:50: PhoenixTokenAuth.Authenticator.generate_unique_token_for/1
        (phoenix_token_auth) lib/phoenix_token_auth/authenticator.ex:42: PhoenixTokenAuth.Authenticator.generate_token_for/1
        (phoenix_token_auth) lib/phoenix_token_auth/users_controller.ex:57: PhoenixTokenAuth.UsersController.confirm/2
        (phoenix_token_auth) lib/phoenix_token_auth/users_controller.ex:1: PhoenixTokenAuth.UsersController.phoenix_controller_pipeline/2
        (echo) lib/phoenix/router.ex:297: Echo.Router.dispatch/2

Travis integration

Since there are more people contributing, it would be good to have some Continuous Integration service building up PRs and the like. Travis is the most famous (IMHO) so I think we should give it a try. Besides this is almost as easy as adding a file (whcich we can base upon plug's for example). After that, we should add the Travis badge to README.

Base 64 encoded key?

This may very well be user error, but I have been trying to use Joken with a JWT generated by a third party (auth0.com) and I am running into some problems verifying the signature.

Running the same token/signature provided through http://jwt.io works, but only if I click the "secret base64 encoded" check box.

Does Joken support encoded secret keys like this?

If it does not yet I am happy to help if given a little guidance (I am pretty unfamiliar with JWT, this is the first time I have used it).

I am using the master branch, not the hex.pm release.

Thanks!

Validate claim

The new config setup is great, but one thing I found left me wanting a little more ;)

When validating claims, there's no way to validate many of them because all you have is the claims themselves and the key to validate. For example if I want to validate the :aud I might have something like:

Joken.decode(string, aud: "MySystem")

Unfortunately as it stands today, I can't check this since the [{aud: "MySystem"}] is not passed through to the validate_claim function.

Another example. Imagine an background worker which needs to call background (s2s) services. The exp should be checked against the time the request was made rather than what the time is now since it may have been sitting in the queue for a while.

I think it should be as simple as passing through the options to the verify_claims functions as a 3rd argument. However this breaks the current contract.

I'd be happy to put a PR together if you think this would be decent.

Define behaviour to handle the addition and/or checking of claims

For instance if someone wants to add claims to a token, or define a custom jti algorithm to be used when encoding a token, etc.

#Should return a new payload with whatever claims added to it
add_claims(payload)

#Encodes the payload. Same as  (and would replace) Joken.Codec.encode
encode(payload)

#Decodes the token. Same as  (and would replace) Joken.Codec.decode
decode(token)

#After the signature has been validated and payload decoded, 
#this would take the payload and validate the claims
validate_claims(payload)

This should allow for more customization. We can add default implementations for some of the claims (i.e. the time based ones), but still allow for a custom algorithm to be used instead.

[Critical] Validations are Skipped if tokens do not contain the validated property

When writing tests for #128, I discovered that validations were ignored if the token payload did not include the field/prop that was being validated. This could accidentally open critical security holes.

For example, if I have a route that issues a token for a regular user:

my_issued_token = %{userId: id}
  |> token
  |> with_signer(hs256("my_secret"))
  |> sign
  |> get_compact

And a route that needs admin privileges to access:

my_verified_token = compact_token
  |> token
  |> with_validation("admin", &(&1 == true))
  |> with_signer(hs256("my_secret"))
  |> verify!

That above token accidentally has admin access. To prevent it, I have to explicitly set the admin property.

my_issued_token = %{userId: id, admin: false}
  |> token
  |> with_signer(hs256("my_secret"))
  |> sign
  |> get_compact

This is problematic for a couple reasons:

  • For security, least privilege should be assumed
  • If tokens are extended they need to be updated everywhere or risk accidental access.

I have a pull request ready to go that patches v1.0.0, v1.1.0, and v1.2.0, so they can all be published right away.

edit and aside: I wanted to have the code ready to go right as this was reported so it could be patched asap. In the future, a private channel for discussing security flaws could included in the README.md so an approach for fixing can be discussed with project maintainers without widely broadcasting a potential hole.

How to use verify?

I am sorry that I am writing this here, as this is not an issue, however I am new to elixir and I need to know how to use verify:

my_verified_token = "my_verified_token "
    |> token
    |> with_signer(hs256("myKey"))
    |> verify

but how to do if statement based on if the token is verified or not?

like:

if verified do
#
else
#
end

Also, how would I get the values of the payload within the token?

thank you

LICENSE file

Right now there is no license in place for this project. I am no expert on this area but just adding a LICENSE file at root and adding a note at README's bottom should suffice.

Do you advocate any specific open source license? If not I guess we should follow Elixir's choice of Apache License 2.0.

I will be glad to do it if that is ok.

Create documentation examples on claims

It would be helpful if document existed for writing the various claim generators.

Here are some examples, but would like comments.

  • nonce (jti claim)

Probably hmac(random number, (hash(identifier) + now_timestamp)) where the random number is the key and the hashed identifier and timestamp is the data.

  • expiration time (exp claim)

with_claim_generator("exp", fn -> current_time + 60 * 1000 end)

  • creation time (iat claim)

with_claim_generator("iat", fn -> current_time end)

  • nbf claim

with_claim_generator("nbf", fn -> current_time - 100 end)

Missing tag 0.15.0

Release 0.15.0 is on Hex, but the tag is missing on github. Could you please add it?

Plug documentation - on_verifying function

I was just updating an old toy project of mine that used Joken plus a Plug that I wrote myself, so I figured I should swap to the Joken.Plug but it took me a while to figure out a few things.

In the documentation and readme it says:

plug Joken.Plug, on_verifying: &verify_function/1

However the arity of verify_function is actually 0

Also it might be good to show an example of what verify_function should do e.g:

  def verify_function() do
    %Joken.Token{}
    |> with_json_module(Poison)
    |> with_signer(hs256(secret_key()))
  end

Also if that is wrong someone let me know but it seemed to work OK :-)

Signature verification is not enforced

Hello! Nice utility library ๐Ÿ‘

I just think that I've found a somewhat strange behaviour. Here, the first thing you do is to split the JWT and if it has only 2 parts you keep on going regardless of the algorithm passed. So, even if I configure it to ONLY use HS256, if I receive a token that has no signature, it will go on as a valid token.

Here's a snippet:
screenshot from 2015-05-13 16 12 58

The second time I decode it, I remove the signature and it decodes properly all the same. If I encode some valid JSON (like changing the sub claim) and simply remove the signature it will pass all the same. Even if the JOSE header has the alg correctly as HS256.

I guess there should be someway to ensure signature verification if the algorithm is passed. Something like:

defp verify_signature(token, key, algorithm) do
    case String.split(token, ".") do
      [ _header64, _payload64 ] ->
          if algorithm == :none || is_nil(algorithm) do
              { :ok, token }
          else
              { :error, "Missing signature" }
      [ header64, payload64, jwt_signature ] ->
        signature = :crypto.hmac(Utils.supported_algorithms[algorithm], key, "#{header64}.#{payload64}")

        if Utils.base64url_encode(signature) == jwt_signature do
            { :ok, token }
        else
            {:error, "Invalid signature"}
        end
      _ ->
        {:error, "Invalid JSON Web Token"}        
    end

  end

If that is ok I can send a PR!

ps256 with x5u header

I have a token that is using ps256 and it has an x5u header with the public key location. How do I encode the key as a map to feed to the ps256 function? Any help would be appreciated.

[Feature request] - Check expired token for refreshing it

It is common the need for refreshing a JWT token. Once an issued token expires the client should be able to renew it with some mechanism. Refreshing is not Joken's purpose but in this scenario we need to check the validity of a token even if it has expired.

The issue is that decoding a token with Joken will give an error in the first claim (exp) that will be piped through all other claims without checking. So, it would be nice to have a way to decode a token and know that all claims BUT exp are valid and refreshing can proceed.

This is perhaps a new function in Joken module that would delegate to Joken.Token.decode with some kind of option. Something like:

# this enforces the caller to know the token may be expired
{:expired, payload} = Joken.decode_expired some_jwt

Or maybe some optional parameter to Joken.decode like:

# optional skip parameter at call site that shows the caller knows what he is doing
{:ok, payload} = Joken.decode some_jwt, skip: [:exp]

Personally, I prefer the second option but that is debatable. If desired I can come up with a PR for these options.

Use string keys by default in new API

I asked about what's the best practice for string keys vs atom keys and the response I got was "string keys for user input, atom keys for everything else". I also know that Guardian, which currently uses this library, uses string keys as well. Right now, the default token uses atoms for the claims and validations and the verify function turns the strings into atoms after JSON decoding.

I think we should use strings for keys by default and only use atoms when the user adds a claim or validation using one, a struct is used, and in the case of verify when a struct is given or an option to use atoms is given.

@cs-victor-nascimento what do you think?
@hassox maybe you would like to chime in as well?

Logger integration?

Most of the time I want to debug something in Joken I end up adding some logging. The Logger framework is awesome and I it is easy to integrate it in an application's config.

When disabling it in config, since all its functions are macros, they are not even included in the final AST. This is real neat!

It includes a per application configuration so we could assume a default to ERROR only and advise in the README that while developing it is good to enable it in a :dev config.

Thoughts?

JWT exp

I have a JWT signature generated by another library and I expect that the JWT library would return "Signature has expired" when i ask for errors..

Is there any plan to enforce the exp claim?

[FEATURE] - Claim generation

While using this new API I stumbled on something we might have missed: claim generation instead of statically linking the claim. The difference is illustrated below:

def with_claim(token, claim, value) do
  # value is always the same. Useful for `issuer` and custom claims.
end

def with_claim_generation(token, claim, function) do
  # Value is always the result of calling function on the time of signing.
  # Useful for exp, iat, nbf.
  # Can be expanded for receiving context on signing through options param.
end

This makes it possible for me to have a single instance of Joken.Token generating tokens for several requests.

What do you think?

Allow configuration of json library

I am a pretty big fan of the elixir json package poison and it gets similar performance to jsx.

Would it be possible to make the json library configurable?

Typos in the README

In the example for My.Json.Module:
@behaviour Joken.Json
should be
@behaviour Joken.Codec

and in the config example, there should be a comma after :joken
so it should be
config :joken,
secret_key: "test"

Invalid signature for base 64 encoded secret

I'm using Auth0 for authentication between phoenix and client side. Auth0 generates a base 64 encoded secret. I can verify the token on jwt.io with the checkbox "secret base64 encoded" checked. I get an invalid signature on jwt.io if I don't check the box.

I'm seeing the same error when using verify method for a token generated by Auth0. Here's the command I use to verify the token:

verify_token = "ey....." |> token |> with_signer(hs256(auth0_secret)) |> verify

I tried doing auth0_secret = Base.decode64("client secret..") but I'm getting an erorr. One thing I noticed that the auth0 secret has "-" character, which fails while decoding with an error: non-alphabet digit found: "-" (byte 45)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.