joken-elixir / joken Goto Github PK
View Code? Open in Web Editor NEWElixir JWT library
License: Apache License 2.0
Elixir JWT library
License: Apache License 2.0
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
.
This includes:
Issuer (iss)
Subject (sub)
Audience (aud)
Expiration (exp)
Not Before (nbf)
Issued At (iat)
JWT ID (jti)
All Defined at: https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25
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.
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!
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.
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:
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!
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?
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
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.
We should use the new verify_strict/3
from JOSE 1.4
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)
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.
What would be needed to make this 1.0?
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:
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
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"))
Hi,
In the example for the JSON module on the README, I think Joken.Json should be Joken.Codec.
In the config section of the README, algorithm is spelled wrong -- 'algorithem'.
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
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.
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!
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.
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.
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:
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.
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
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.
It would be helpful if document existed for writing the various claim generators.
Here are some examples, but would like comments.
Probably hmac(random number, (hash(identifier) + now_timestamp)) where the random number is the key and the hashed identifier and timestamp is the data.
with_claim_generator("exp", fn -> current_time + 60 * 1000 end)
with_claim_generator("iat", fn -> current_time end)
with_claim_generator("nbf", fn -> current_time - 100 end)
Release 0.15.0 is on Hex, but the tag is missing on github. Could you please add it?
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 :-)
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.
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!
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.
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.
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?
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?
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?
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?
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?
https://www.timmclean.net/2015/03/31/jwt-algorithm-confusion.html
** edited mistakenly assumed :none might match with a signature of "" **
might be nice to be able to ensure :none never will verify :ok so you can prevent the use of :none via Mix.Config or something.
wondering if the other issues in the above article have been considered.
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"
This will be just like get_claims
except it will return the errors from token verification.
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)
Add config option to control the ability to use the 'none' algorithm. Set to false by default.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.