Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
229 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,5 @@ | |
/deps | ||
erl_crash.dump | ||
*.ez | ||
|
||
/doc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2015 Gary Fleshman | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,65 @@ | ||
JsonWebToken | ||
============ | ||
# JSON Web Token | ||
|
||
** TODO: Add description ** | ||
## A JSON Web Token implementation for Elixir | ||
|
||
### Description | ||
An Elixir implementation of the JSON Web Token (JWT) Standards Track [RFC 7519][rfc7519] | ||
|
||
### Philosophy & design goals | ||
* Minimal API surface area | ||
* Clear separation and conformance to underlying standards | ||
- JSON Web Signature (JWS) Standards Track [RFC 7515][rfc7515] | ||
- JSON Web Algorithms (JWA) Standards Track [RFC 7518][rfc7518] | ||
* Thorough test coverage | ||
* Modularity for comprehension and extensibility | ||
* Fail fast and hard, with maximally strict validation | ||
- Inspired by [The Harmful Consequences of Postel's Maxim][thomson-postel] | ||
* Implement only the REQUIRED elements of the JWT standard (initially) | ||
|
||
## Usage | ||
|
||
### JsonWebToken.sign(claims, options) | ||
|
||
Returns a JSON Web Token string | ||
|
||
`claims` (required) map | ||
|
||
`options` (required) map | ||
|
||
* **alg** (optional, default: `HS256`) | ||
* **key** (required unless alg is 'none') | ||
|
||
### JsonWebToken.verify(jwt, options) | ||
|
||
Returns either: | ||
* a JWT claims set map, if the Message Authentication Code (MAC), or signature, is verified | ||
* a string, 'Invalid', otherwise | ||
|
||
`jwt` (required) is a JSON web token string | ||
|
||
`options` (required) map | ||
|
||
* **alg** (optional, default: `HS256`) | ||
* **key** (required unless alg is 'none') | ||
|
||
### Supported encryption algorithms | ||
The 2 REQUIRED JWT algorithms | ||
|
||
- HMAC using SHA-256 per [RFC 2104][rfc2104] | ||
- none (unsecured) | ||
|
||
### Supported Elixir versions | ||
Elixir 1.0.5 and up | ||
|
||
### Limitations | ||
Future implementation may include these features: | ||
|
||
- Representation of a JWT as a JSON Web Encryption (JWE) [RFC 7516][rfc7516] | ||
- RECOMMENDED or OPTIONAL encryption algorithms | ||
- OPTIONAL nested JWTs | ||
|
||
[rfc2104]: http://tools.ietf.org/html/rfc2104 | ||
[rfc7515]: http://tools.ietf.org/html/rfc7515 | ||
[rfc7516]: http://tools.ietf.org/html/rfc7516 | ||
[rfc7518]: http://tools.ietf.org/html/rfc7518 | ||
[rfc7519]: http://tools.ietf.org/html/rfc7519 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
defmodule JsonWebToken.Format.Base64Url do | ||
@moduledoc """ | ||
Provide base64url encoding and decoding functions without padding, based upon standard base64 encoding | ||
and decoding functions that do use padding | ||
see http://tools.ietf.org/html/rfc7515#appendix-C | ||
""" | ||
|
||
@doc """ | ||
Given a string, return a url_encode64 string with all trailing "=" padding removed | ||
## Example | ||
iex> JsonWebToken.Format.Base64Url.encode("foo") | ||
"Zm9v" | ||
""" | ||
def encode(string) do | ||
string | ||
|> Base.url_encode64 | ||
|> base64_padding_removed | ||
end | ||
|
||
defp base64_padding_removed(encoded), do: String.rstrip(encoded, ?=) | ||
|
||
@doc """ | ||
Given a string encoded as url_encode64, add trailing "=" padding and return a decoded string | ||
## Example | ||
iex> JsonWebToken.Format.Base64Url.decode("YmFy") | ||
"bar" | ||
The number of "=" padding characters that need to be added to the end of a url_encode64-encoded | ||
string without padding to turn it into one with padding is a deterministic function of the length | ||
of the encoded string. | ||
""" | ||
def decode(string) do | ||
string | ||
|> base64_padding_added | ||
|> Base.url_decode64! | ||
end | ||
|
||
defp base64_padding_added(str) do | ||
mod = rem(String.length(str), 4) | ||
str <> padding(mod) | ||
end | ||
|
||
defp padding(0), do: "" | ||
defp padding(1), do: raise "Invalid base64 string" | ||
defp padding(mod), do: String.duplicate("=", (4 - mod)) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
%{"earmark": {:hex, :earmark, "0.1.17"}, | ||
"ex_doc": {:hex, :ex_doc, "0.7.3"}, | ||
"excoveralls": {:hex, :excoveralls, "0.3.11"}, | ||
"exjsx": {:hex, :exjsx, "3.2.0"}, | ||
"hackney": {:hex, :hackney, "1.2.0"}, | ||
"idna": {:hex, :idna, "1.0.2"}, | ||
"jsx": {:hex, :jsx, "2.6.2"}, | ||
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
defmodule JsonWebToken.Format.Base64UrlTest do | ||
use ExUnit.Case | ||
|
||
alias JsonWebToken.Format.Base64Url | ||
|
||
doctest Base64Url | ||
|
||
defp decode_encoded_matches?(str) do | ||
encoded = Base64Url.encode(str) | ||
str == Base64Url.decode(encoded) | ||
end | ||
|
||
test "decode/1 encode/1 typical" do | ||
assert decode_encoded_matches?("{\"typ\":\"JWT\", \"alg\":\"HS256\"}") | ||
end | ||
|
||
test "decode/1 encode/1 w whitespace" do | ||
assert decode_encoded_matches?("{\"typ\":\"JWT\" , \"alg\":\"HS256\" }") | ||
end | ||
|
||
test "decode/1 encode/1 w line feed and carriage return" do | ||
assert decode_encoded_matches?("{\"typ\":\"JWT\",/n \"a/rlg\":\"HS256\"}") | ||
end | ||
|
||
defp given_encoded_matches?(str, encoded) do | ||
Base64Url.encode(str) == encoded && | ||
Base64Url.decode(encoded) == str | ||
end | ||
|
||
test "decode/1 w no padding char" do | ||
str = "{\"typ\":\"JWT\", \"alg\":\"none\"}" | ||
encoded = "eyJ0eXAiOiJKV1QiLCAiYWxnIjoibm9uZSJ9" | ||
assert given_encoded_matches?(str, encoded) | ||
end | ||
|
||
test "decode/1 w 1 padding char present" do | ||
str = "{\"typ\":\"JWT\", \"alg\":\"algorithm\"}" | ||
encoded = "eyJ0eXAiOiJKV1QiLCAiYWxnIjoiYWxnb3JpdGhtIn0=" | ||
assert Base64Url.decode(encoded) == str | ||
end | ||
|
||
test "decode/1 w 1 padding char removed" do | ||
str = "{\"typ\":\"JWT\", \"alg\":\"algorithm\"}" | ||
encoded = "eyJ0eXAiOiJKV1QiLCAiYWxnIjoiYWxnb3JpdGhtIn0" | ||
assert given_encoded_matches?(str, encoded) | ||
end | ||
|
||
test "decode/1 w 2 padding char present" do | ||
str = "{\"typ\":\"JWT\", \"alg\":\"HS256\"}" | ||
encoded = "eyJ0eXAiOiJKV1QiLCAiYWxnIjoiSFMyNTYifQ==" | ||
assert Base64Url.decode(encoded) == str | ||
end | ||
|
||
test "decode/1 w 2 padding char removed" do | ||
str = "{\"typ\":\"JWT\", \"alg\":\"HS256\"}" | ||
encoded = "eyJ0eXAiOiJKV1QiLCAiYWxnIjoiSFMyNTYifQ" | ||
assert given_encoded_matches?(str, encoded) | ||
end | ||
|
||
test "decode/1 w invalid encoding" do | ||
message = "Invalid base64 string" | ||
assert_raise RuntimeError, message, fn -> | ||
Base64Url.decode("InR5cCI6IkpXVCIsICJhbGciOiJub25lI") | ||
end | ||
end | ||
end |