a jwt finger printer
jwt evolved out of a family of web oriented specfications targeting the second major version of oauth, oauth2 as a means of encoding structured data in a compact and tamper-proof format.
In short, jwt defines a standard means of signing arbirary data in JSON format.
At the following to your sbt project's build.sbt
resolvers += "softprops-maven" at "http://dl.bintray.com/content/softprops/maven"
libraryDependencies += "me.lessis" %% "prints" % "0.1.0"
A jwt token is made up of 3 parts. A (JOSE) header declaring information about how verify the signature of a set of claims, a set of claims this can actually be any arbitrary JSON data, and a signature, generated with a private key using a method specified in header information.
The only required property of a header is a signing algorithm identifier. This library current supports "none" (no signing), "HS256" (HmacSHA256), "HS384" (HmacSHA384), "HS512" (HmacSHA512), "RS256" (SHA256withRSA), "RS384" (SHA384withRSA), and "RS512" (SHA512withRSA). There's room to support more. Feel free to open a pull request and do so!
val header = prints.Header("HS256")
Claims represent some metadata meant for passing between two parties. In practice this can be any arbitrary data. Prints provides factory methods for creating sets of claims from simple String
key-value pairs or a org.json4s.JValue
value.
val simpleClaims = prints.Claims(
"foo" -> "bar"
)
import org.json4s._
import org.json4s.JsonDSL._
val complexClaims = prints.Claims(
("foo" -> "bar") ~
("nbf" -> notBeforeTimestamp) ~
("scope" -> List("read", "write"))
)
Because claims can contain arbitrary constraints, a prints.Claims
instance provides a simple
query interface with a few typed helpers. A primative query method get
defined as
def get(f: JField => Boolean): Option[JValue]
provides a base for typed helpers like str
(returns option of String), long
(returns option of Long), seconds
returns a finite duration representing seconds. This typed query methods are all implemented in terms of get
.
val bar = complexClaims.str("foo") // Some("bar")
val scopes = complexClaims.get(_ match {
case ("scope", JArray(_)) => true
case _ => false
}).collect {
case JArray(scopes) => for {
JString(scope) <- scopes
} yield scope
} // Some(List("read", "write"))
Given a Header
, set of Claims
and a private key you can then create a signed token. The type of header algorithm used dictates the type of key used
to sign a set of claims. An HS*
header can be signed with an Algorithm.Key.Bytes
key and a RS*
header can be signed with an Algorithm.Key.Rsa` key.
The result is an Option
type because a header-defined signing algorithm may not be supported
val token: Option[Array[Byte]] =
prints.JWT(header, claims, prints.Algorithm.Key.Bytes("secret".getBytes))
A valid result can then be safely used for HTTP or other transports. The result is URL safe so no additional encoding is required.
token.map(new String(_)) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ==.88HN1LmGMYQTD4CYwnOoM9EqFWqSv6G1kkGI0EjNOmA=
Prints provides an extractor for jwt strings that unpacks the token into the 3 canonical parts. A Header
, a Claims
set, and a string signature
val jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ==.88HN1LmGMYQTD4CYwnOoM9EqFWqSv6G1kkGI0EjNOmA="
val decoded = jwt match {
case prints.JWT(header, claims, sig) =>
// to application specific validation
case _ => None
}
You can verify jwt tokens in two forms by supplying a string jwt and key or the tuple of unpacked components and key. For former is useful if you
know the key a head of time, otherwise you may more commonly derive a key to verify based on the aud
of the claim, in which case you need to first
unpack the jwt components. In each case the result is Option of the unpacked contents of the jwt
val validated: Option[(Header, Claims, Array[Byte])] = prints.JWT.verify(jwt, algo, key)
Doug Tangren (softprops) 2015