Packages
joken
0.1.0
2.6.2
2.6.1
2.6.0
2.5.0
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.1
2.0.0
2.0.0-rc3
2.0.0-rc2
2.0.0-rc1
2.0.0-rc0
1.5.0
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
0.16.1
0.16.0
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.8.1
0.8.0
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.1.0
JWT (JSON Web Token) library for Elixir.
Current section
Files
Jump to
Current section
Files
lib/joken.ex
defmodule Joken do
use Jazz
@moduledoc """
Encodes and decodes JSON Web Tokens.
iex(1)> Joken.encode(%{username: "johndoe"}, "secret", :HS256, %{})
{:ok,
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG5kb2UifQ.OFY_3SbHl2YaM7Y4Lj24eVMtcDaGEZU7KRzYCV4cqog"}
iex(2)> Joken.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImpvaG5kb2UifQ.OFY_3SbHl2YaM7Y4Lj24eVMtcDaGEZU7KRzYCV4cqog")
{:ok, %{username: "johndoe"}}
"""
def encode(payload, key, :HS256, headers) do
do_encode(payload, key, :HS256, headers)
end
def encode(payload, key, :HS384, headers) do
do_encode(payload, key, :HS384, headers)
end
def encode(payload, key, :HS512, headers) do
do_encode(payload, key, :HS512, headers)
end
defp do_encode(payload, key, alg, headers) do
{_, headerJSON} = Map.merge(%{ alg: to_string(alg), "typ": "JWT"}, headers) |> JSON.encode
{status, payloadJSON} = JSON.encode(payload)
case status do
:error ->
{:error, "Error encoding map to JSON"}
:ok ->
header64 = base64url_encode(headerJSON)
payload64 = base64url_encode(payloadJSON)
hash_alg = alg_to_hash_alg(alg)
signature = :crypto.hmac(hash_alg, key, "#{header64}.#{payload64}")
signature64 = base64url_encode(signature)
{:ok, "#{header64}.#{payload64}.#{signature64}"}
end
end
def decode(jwt, key) do
{status, data} = get_data(jwt)
case status do
:error ->
{status, data}
:ok ->
payload = Enum.fetch!(data, 1)
cond do
verify(data, key) == false ->
{:error, "Verification failed"}
is_expired(payload) == true ->
{:error, "Token is expired"}
true ->
{:ok, payload}
end
end
end
defp get_data(jwt) do
values = String.split(jwt, ".")
if Enum.count(values) != 3 do
{:error, "Invalid JSON Web Token"}
else
decoded_data = Enum.map_reduce(values, 0, fn(x, acc) ->
cond do
acc < 2 ->
data = base64url_decode(x)
{_ , map} = JSON.decode(data, keys: :atoms)
{ map , acc + 1}
true ->
{x, acc + 1}
end
end)
{decoded, _} = decoded_data
{:ok, decoded}
end
end
defp verify(data, key) do
header = Enum.fetch!(data, 0)
payload = Enum.fetch!(data, 1)
jwt_signature = Enum.fetch!(data, 2)
header64 = header |> JSON.encode! |> base64url_encode
payload64 = payload |> JSON.encode! |> base64url_encode
hash_alg = header.alg |> String.to_atom |> alg_to_hash_alg
signature = :crypto.hmac(hash_alg, key, "#{header64}.#{payload64}")
base64url_encode(signature) == jwt_signature
end
defp is_expired(payload) do
if Map.has_key?(payload, :exp) do
payload.exp < get_current_time()
else
false
end
end
def get_current_time() do
{mega, secs, _} = :os.timestamp()
mega * 1000000 + secs
end
defp alg_to_hash_alg(alg) do
case alg do
:HS256 -> :sha256
:HS384 -> :sha384
:HS512 -> :sha512
end
end
defp base64url_encode(data) do
data
|> :base64.encode_to_string
|> to_string
|> String.replace(~r/[\n\=]/, "")
|> String.replace(~r/\+/, "-")
|> String.replace(~r/\//, "_")
end
defp base64url_decode(data) do
base64_bin = String.replace(data, "-", "+") |> String.replace("_", "/")
base64_bin = base64_bin <> case rem(byte_size(base64_bin),4) do
2 -> "=="
3 -> "="
_ -> ""
end
:base64.decode_to_string(base64_bin) |> to_string
end
end