don't get to do all that much coding anymore. When I do end up with a coding project, Sammy helps me out from her cushion under my desk This is my dog, Sammy a/k/a @sammyGenehack JWTs WIYL! - BeerCityCode 2017 – @genehack 4
(kinda, sorta) • ...that also works with CLI, mobile, or even desktop apps • An authoriza.on or access control mechanism • ...kinda like OAuth but without losing your will to live • Cross-domain friendly JWTs WIYL! - BeerCityCode 2017 – @genehack 11
parts in a bit more detail What do they look like? • dot-delimited string ('.') • 3 parts • header • payload • signature • Example: xxx.yyyyy.zzz JWTs WIYL! - BeerCityCode 2017 – @genehack 13
in this talk -- maybe a bit too much code -- but I really wanted to drive home how simple and elegant JWTs are, and showing how they actually work on a code level is the best way to do that, IMO In real practice, you'd almost certainly be using a library for most of the code I'm showing, but since the point is how uncomplicated most of these operations are, I wanted to give you some idea of what was happening inside that code For each code sample, I'm going to show the whole code sample -- and it's going to be way too small to read. I'm doing that just so you can see, it's really not that much code. We'll then step through each one in much smaller 3 or 4 line chunks. A word about my code samples JWTs WIYL! - BeerCityCode 2017 – @genehack 17
varying degrees of support Libraries for DAYS • .NET, Python, Node, Java, Javascript, Ruby, Perl, Go, PHP • Haskell, Rust, Lua, Scala, Clojure, ObjecDveC, SwiF, Delphi • Support for your favorite language/pla3orm is probably not an issue JWTs WIYL! - BeerCityCode 2017 – @genehack 29
Now that we were past the RFC reading stage, and she'd seen how simple and elegant JWTs were conceptually, she starting asking... OK, you've got my a"en%on JWTs WIYL! - BeerCityCode 2017 – @genehack 30
pretty flexible. but one way you'll probably end up using them is as part of a fairly standard authentication/ authorization type flow Basic auth/authz usage (image stolen from jwt.io) JWTs WIYL! - BeerCityCode 2017 – @genehack 32
…don't send anything sensi6ve! • Need to control expira6on, re-issue, etc. • Some APIs will send a fresh JWT to the client per-request • Sites other than issuing site can receive JWT • …but they must share the secret to validate JWTs WIYL! - BeerCityCode 2017 – @genehack 33
methods: • As part of the URL in a GET • In a POST body • In the Authorization header using Bearer scheme: Authorization: Bearer <token> JWTs WIYL! - BeerCityCode 2017 – @genehack 34
the actual implementation so she can really understand what's going on... How would you actually use this in an app? JWTs WIYL! - BeerCityCode 2017 – @genehack 35
have to declare a route Then we import a helper library that's just a thin wrapper around the jsonwebtoken NPM library. Generate a token on login app.post('/user/login', app.wrap(user_login)); var jwt = require('jwt'); // helper wrapper around 'jsonwebtoken' function * user_login (req, res) { if (! (req.body.email && req.body.password)) { res.status(400); res.json({message: 'invalid request'}); return; } JWTs WIYL! - BeerCityCode 2017 – @genehack 37
and return the token in a header var token = jwt.sign(claims); res.append('X-MyApp-Token', token); res.status(200); } JWTs WIYL! - BeerCityCode 2017 – @genehack 39
a middleware. That'll make sure it happens on every request. Validate with a middleware // enable JWT-verification middleware var jwt = require('jwt'); // helper wrapper around 'jsonwebtoken' app.use(function (req, res, next) { // initialize the jwt object req.jwt = {}; // now parse the Authorization header if it exists Promise.resolve(req.headers.authorization).then(function (auth) { // If the Authorization header is present and employs the correct // Bearar scheme, extract the token and attempt to verify it. if (auth) { var scheme = auth.split(' ')[0]; var token = auth.split(' ')[1]; if (scheme == 'Bearer') { return jwt.verify(token).catch(function (error) { throw new Error('failed to verify claim'); }); } } throw new Error('authorization not attempted'); }) .then(function (payload) { req.jwt = payload; next(); }) .catch(function (error) { // Allow login without JWT if (req.path == '/user/login' && req.method == 'POST') { return next(); } res.status(401); res.header('WWW-Authenticate', 'Bearer realm=myapp'); res.json({ message: 'authorization required' }); }); }); JWTs WIYL! - BeerCityCode 2017 – @genehack 41
if it exists Promise.resolve(req.headers.authorization).then(function (auth) { // If the Authorization header is present and employs the correct // Bearar scheme, extract the token and attempt to verify it. if (auth) { var scheme = auth.split(' ')[0]; var token = auth.split(' ')[1]; if (scheme === 'Bearer') { return jwt.verify(token).catch(function (error) { throw new Error('failed to verify claim'); }); } } throw new Error('authorization not attempted'); }) JWTs WIYL! - BeerCityCode 2017 – @genehack 43
was making plans to use JWTs in all her future projects. But she wondered if there was anything else JWTs could do for her... That's cool. What else you got? JWTs WIYL! - BeerCityCode 2017 – @genehack 46
a request that looks like: GET /content/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJpYXQiOjE0OTcxMDUxMTcsImV4cCI6MTQ5NzEwNTE3Ny wiZG9jIjozNDUzNDV9.fKKCZgc2ckeONF9ELOcc9EgS-UPr CBX2bwoPmxjStdQ • Extract the token and validate • Get the document ID from the payload • Send back that document! JWTs WIYL! - BeerCityCode 2017 – @genehack 55
to give this talk, because JWTs provide an easy solution for a problem that I feel like I've run into time and time again in my coding career Recurring dilemma: 'lightweight' access control JWTs WIYL! - BeerCityCode 2017 – @genehack 57
user management, admin screens, etc. or oauth -- but i've never had a good experience using oauth. anybody here like oauth? Recurring dilemma: 'lightweight' access control • Op$on 1: leave it wide open • a/k/a the MongoDB or WTF,YOLO! paAern • Op$on 2: implement full authn/authz subsystem • …again • Op$on 3: OAuth !!!!!! JWTs WIYL! - BeerCityCode 2017 – @genehack 58
• You don't want to make anybody authen;cate to use it • You don't want it wide open to the Internet either • a/k/a authz without authn JWTs WIYL! - BeerCityCode 2017 – @genehack 62
previous scenario: RSA key-pair • Can include the public key in the JWT header using JWK • JSON Web Key, natch • Allows API client to produce claims in a verifiable way JWTs WIYL! - BeerCityCode 2017 – @genehack 63
RSA key-pair • Record the fingerprint of the public key (important later!) • You can even let the client generate the key-pair • You just need the public key fingerprint JWTs WIYL! - BeerCityCode 2017 – @genehack 64
the private key to sign • They include the public key in the header • Include iat (issued-at) and exp (expires) claims • Send JWT in with API request JWTs WIYL! - BeerCityCode 2017 – @genehack 65
of the header • Validate the signature using the public key • Validate that public key fingerprint is white-listed • Signature produced with private key • Public key is white-listed • Therefore we know JWT is valid! JWTs WIYL! - BeerCityCode 2017 – @genehack 66
validate iat and exp and any other rules • Your library should probably do that stuff for you, mostly • Again, nothing is encrypted, so don't plan on sensi've stuff in the payload or header JWTs WIYL! - BeerCityCode 2017 – @genehack 67
directory as your code... Client side code use Crypt::JWT qw(encode_jwt); use Crypt::PK::RSA; use HTTP::Request; my $pri_key = Crypt::PK::RSA->new('./key.pri'); my $pub_key = Crypt::PK::RSA->new('./key.pub'); JWTs WIYL! - BeerCityCode 2017 – @genehack 69
Try::Tiny; my $auth_header = request_header 'Authorization' ; my $token; status_401 unless ( $token ) = $auth_header =~ /^Bearer (.*)$/; # try to decode it and confirm valid sig, # and valid iat and exp claims my( $header, $payload ); try { ( $header, $payload ) = decode_jwt( token => $token , decode_header => 1 , accepted_alg => 'RS512' , verify_iat => 1 , verify_exp => 1 ); }; # no catch block, just drop the error, we're out of here in that case status_401 unless $header and $payload; # check that expiration time is less than one hour status_401 unless $payload->{exp} - $payload->{iat} < 3600; # check that the included public key is on the whitelist my $pk = Crypt::PK::RSA->new; $pk->import_key($header->{jwk}); my $thumbprint = $pk->export_key_jwk_thumbprint; status_401 unless config->{whitelist}{$thumbprint}; # if we get here, we're all good! ... JWTs WIYL! - BeerCityCode 2017 – @genehack 73
FAILS • Not right algorithm? FAILS • Doesn't have iat and exp? FAILS ALL that valida)on is happening inside the library, so I don't have to worry about it. • Me? WINS JWTs WIYL! - BeerCityCode 2017 – @genehack 76
docs that tokens can only be valid for one hour • Have to check that ourselves • Also need to make sure this isn't some random RSA keypair • Need to make sure we know this public key JWTs WIYL! - BeerCityCode 2017 – @genehack 77
an expires time more than 1 hour into the future. we also need to make sure the public key fingerprint is on the allowed list one weakness with this scheme is, if a valid token leaks, that allows access to the API until it expires -- so make sure you chose an allowable access window based on an evaluation of that potential impact API side: more valida0on # check that expiration time is less than one hour status_401 unless $payload->{exp} - $payload->{iat} < 3600; # check that the included public key is on the whitelist my $pk = Crypt::PK::RSA->new; $pk->import_key($header->{jwk}); my $thumbprint = $pk->export_key_jwk_thumbprint; status_401 unless config->{whitelist}{$thumbprint}; JWTs WIYL! - BeerCityCode 2017 – @genehack 78
we're all good! • We know the public key in the header by its fingerprint, • so we know the private key was used to sign the JWT • (or it wouldn't validate) • and therefore the JWT is from the private key holder • (who is, by definiAon, authorized!) JWTs WIYL! - BeerCityCode 2017 – @genehack 79
keeping the private key actually private …but revoca,on is as simple as removing the fingerprint from the whitelist. JWTs WIYL! - BeerCityCode 2017 – @genehack 80
solve them in a pre7y elegant way. • This is really pre7y damn cool!!! • You should think about using JWTs. JWTs WIYL! - BeerCityCode 2017 – @genehack 84