Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JWT - To authentication & beyond!

JWT - To authentication & beyond!

Tokens are widely used to identify resources and try to add some security to insecure environments, but sometimes the management of those identifiers can get a bit complex - even more on distributed systems. What if we could have an intelligent token, one that simplifies the way things works without losing integrity or security?
In this talk we present JSON Web Tokens as an alternative for smart and self contained tokens, explaining how to use each claim and giving some common use cases.

Luís Cobucci

August 31, 2017
Tweet

More Decks by Luís Cobucci

Other Decks in Technology

Transcript

  1. Browser Server DB 1. presents credentials 2. validates and starts

    a session 200 OK Set-Cookie: PHPSESSIONID=ABC123; Domain=foo.bar; Secure; HttpOnly; Expires=Thu, 1 Jun 2017 12:00:00 GMT
  2. Browser Server DB 1. presents credentials 2. validates and starts

    a session 200 OK Set-Cookie: PHPSESSIONID=ABC123; Domain=foo.bar; Secure; HttpOnly; Expires=Thu, 1 Jun 2017 12:00:00 GMT
  3. Browser Server DB 1. presents credentials 2. validates and starts

    a session 3. sends cookies on next requests GET / Cookie: PHPSESSIONID=ABC123
  4. Browser Server DB 1. presents credentials 2. validates and starts

    a session 3. sends cookies on next requests 4. reads session data and returns a specific response for logged user 200 OK Hello John!
  5. “ (…) Each request from any client contains all the

    information necessary to service the request, and session state is held in the client. Representational State Transfer - Wikipedia
  6. { "token": "abc123", "uid": 1,
 "expiration": "…", "scope": ["a", "b",

    "c"] } { "token": "def123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] }
  7. { "token": "abc123", "uid": 1,
 "expiration": "…", "scope": ["a", "b",

    "c"] } { "token": "def123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] }
  8. { "token": "abc123", "uid": 1,
 "expiration": "…", "scope": ["a", "b",

    "c"] } { "token": "def123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "def789", "uid": 6,
 "expiration": "…", "scope": ["a"] } { "token": "ghi123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "jkl789", "uid": 6,
 "expiration": "…", "scope": ["a"] }
  9. { "token": "abc123", "uid": 1,
 "expiration": "…", "scope": ["a", "b",

    "c"] } { "token": "def123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "def789", "uid": 6,
 "expiration": "…", "scope": ["a"] } { "token": "ghi123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "jkl789", "uid": 6,
 "expiration": "…", "scope": ["a"] } { "token": "abc123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "def789", "uid": 6,
 "expiration": "…", "scope": ["a"] } { "token": "ghi123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "jkl789", "uid": 6,
 "expiration": "…", "scope": ["a"] } { "token": "abc123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "def789", "uid": 6,
 "expiration": "…", "scope": ["a"] } { "token": "ghi123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "jkl789", "uid": 6,
 "expiration": "…", "scope": ["a"] } { "token": "abc123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "abc789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "def789", "uid": 6,
 "expiration": "…", "scope": ["a"] } { "token": "ghi123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi456", "uid": 3,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "jkl456", "uid": 4,
 "expiration": "…", "scope": ["a", "b"] } { "token": "ghi789", "uid": 5,
 "expiration": "…", "scope": ["a", "b"] } { "token": "jkl789", "uid": 6,
 "expiration": "…", "scope": ["a"] }
  10. + → -
 / → _
 = → (removed) TeSJWlQ/

    S4YaOgK5tz7j+3KxBA g3HTONa9NP80R+9mY= TeSJWlQ_S4YaOgK5tz7 j-3KxBAg3HTONa9NP80 R-9mY
  11. function base64url_decode(string $data): string {
 if ($remainder = strlen($data) %

    4) { $data .= str_repeat('=', 4 - $remainder); } return base64_decode(
 strtr($data, '-_', '+/') ); }
  12. 1. presents credentials 2. validates and creates a token Client

    API - issuer: auth.example.com - permitted to: client.example.com - expires in 300 seconds DB
  13. 1. presents credentials 2. validates and creates a token 201

    Created 
 {
 "token": "…"
 } Client API DB
  14. 1. presents credentials 2. validates and creates a token 3.

    sends the issued token GET / Authorization: … Client API DB
  15. 1. presents credentials 2. validates and creates a token 3.

    sends the issued token 4. verifies the signature, validates the claims and processes the request - is it valid? - client allowed? - expected issuer? - can it be used at this moment? Client API DB
  16. ! 1. cannot store private information in the session
 


    2. sessions cannot be invalidated 
 3. increased network traffic
 
 4. race conditions with highly concurrent HTTP requests writing to session
 
 5. limit on the amount of data stored in session
  17. headers {
 "typ": "JWT",
 "alg": "none"
 } {
 "user": {


    "id": 1,
 "name": "Luís Cobucci"
 }
 }
  18. headers {
 "typ": "JWT",
 "alg": "none"
 } {
 "user": {


    "id": 1,
 "name": "Luís Cobucci"
 }
 } claims
  19. headers {
 "typ": "JWT",
 "alg": "none"
 } {
 "user": {


    "id": 1,
 "name": "Luís Cobucci"
 }
 } claims
  20. Base64URL( )
 + "." + 
 Base64URL( ) headers claims

    payload = alg( , ) payload key signature
  21. Base64URL( )
 + "." + 
 Base64URL( )
 + "."

    + 
 Base64URL( ) headers claims signature
  22. function jwt_create( array $headers, array $claims, string $key ): string

    {
 $headers = base64url_encode(json_encode($headers));
 $claims = base64url_encode(json_encode($claims)); $payload = $headers . '.' . $claims; $signature = base64url_encode(
 hash_hmac('sha256', $payload, $key, true)
 ); return $payload . '.' . $signature; }
  23. HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 PS256

    PS384 PS512 none HMAC RSA ECDSA RSASSA-PSS
  24. HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 PS256

    PS384 PS512 none HMAC RSA ECDSA RSASSA-PSS
  25. HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 PS256

    PS384 PS512 none HMAC RSA ECDSA RSASSA-PSS
  26. headers {
 "typ": "JWT",
 "alg": "HS256"
 } {
 "user": {


    "id": 1,
 "name": "Luís Cobucci"
 }
 } claims key Hello JWT+JWS!
  27. declare(strict_types=1); require 'vendor/autoload.php'; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Rsa\Sha256; return

    Configuration::forAsymmetricSigner( new Sha256(), new Key('file://private.pem', 'testing'), new Key('file://public.pem') );
  28. declare(strict_types=1); /** @var \Lcobucci\JWT\Configuration $config */ $config = require 'config.php';

    $signer = $config->getSigner(); $key = $config->getSigningKey(); $token = $config->createBuilder() ->withClaim('uid', 1) ->getToken($signer, $key);
  29. declare(strict_types=1); /** @var \Lcobucci\JWT\Configuration $config */ $config = require 'config.php';

    $signer = $config->getSigner(); $key = $config->getSigningKey(); $token = $config->createBuilder() ->withClaim('uid', 1) ->identifiedBy(bin2hex(random_bytes(16))) ->getToken($signer, $key);
  30. declare(strict_types=1); /** @var \Lcobucci\JWT\Configuration $config */ $config = require 'config.php';

    $signer = $config->getSigner(); $key = $config->getSigningKey(); $token = $config->createBuilder() ->withClaim('uid', 1) ->identifiedBy(bin2hex(random_bytes(16))) ->issuedBy('https://foo.bar') ->getToken($signer, $key);
  31. declare(strict_types=1); /** @var \Lcobucci\JWT\Configuration $config */ $config = require 'config.php';

    $signer = $config->getSigner(); $key = $config->getSigningKey(); $token = $config->createBuilder() ->withClaim('uid', 1) ->identifiedBy(bin2hex(random_bytes(16))) ->issuedBy('https://foo.bar')
 ->permittedFor('https://client1.bar')
 ->permittedFor('https://client2.bar') ->getToken($signer, $key);
  32. declare(strict_types=1); /** @var \Lcobucci\JWT\Configuration $config */ $config = require 'config.php';

    $signer = $config->getSigner(); $key = $config->getSigningKey(); $now = new DateTimeImmutable(); $token = $config->createBuilder() ->withClaim('uid', 1) ->identifiedBy(bin2hex(random_bytes(16))) ->issuedBy('https://foo.bar')
 ->permittedFor('https://client1.bar')
 ->permittedFor('https://client2.bar')
 ->issuedAt($now)
 ->canOnlyBeUsedAfter($now->modify('+5 minutes'))
 ->expiresAt($now->modify('+1 hour')) ->getToken($signer, $key);
  33. {
 "typ": "JWT",
 "alg": "RS256"
 } {
 "uid": 1, "jti":

    "aa397c06d50fda662ae0da895687f767", "iss": "https://foo.bar", "aud": ["https://client1.bar", “https://client2.bar”], "iat": "1495178904.868879", "nbf": "1495179204.868879", "exp": "1495182504.868879"
 }
  34. declare(strict_types=1); /** @var \Lcobucci\JWT\Configuration $config */ $config = require 'config.php';

    $jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjEsImp0aSI6ImFh' . 'Mzk3YzA2ZDUwZmRhNjYyYWUwZGE4OTU2ODdmNzY3IiwiaXNzIjoiaHR0cHM6L' . 'y9mb28uYmFyIiwiYXVkIjpbImh0dHBzOi8vY2xpZW50MS5iYXIiLCJodHRwcz' . 'ovL2NsaWVudDIuYmFyIl0sImlhdCI6IjE0OTUxNzg5MDQuODY4ODc5IiwibmJ'
 . 'mIjoiMTQ5NTE3OTIwNC44Njg4NzkiLCJleHAiOiIxNDk1MTgyNTA0Ljg2ODg3' . 'OSJ9.jwXzXjm8cU92yxP3XcENg_ZnDvW1MkRTzSoaAwOYCTlSdQ5rv-dCLn_7'
 . '_XPLHSuiACt_aFTnB093GYTpJQKRnqIFPYteK2jVnQALXNPxntnp-v6SMiFBx'
 . 'ofCaVSjgKTWdqkWB4agWrTR77HK_iKdFoZMIdpr8UUBJatkc_MCoDvDMtuDRX'
 . 'wIEBfjs9baICtBvTZyDD7iiMmF4F_qvp2mWd_Qy93gZCrePKAJsgY-sujg84i'
 . 'QFOs-6I3GjybzA0U0Y_bTmCmQHfhRUX5_gL21bZxBFef38OFKW73VxehBxM4O'
 . 'k_nWRbGY7ehsMBshXkJQfp97TJ1cV35a9zyAVXC04A';
 
 $token = $config->getParser()->parse($jwt);
  35. declare(strict_types=1); use Lcobucci\Clock\SystemClock;
 use Lcobucci\JWT\Validation\Constraint; /** @var \Lcobucci\JWT\Configuration $config */

    $config = require 'config.php'; $signer = $config->getSigner(); $key = $config->getVerificationKey();
 $token = $config->getParser()->parse('eyJ0eNiJ9 (...)'); $constraints = [ new Constraint\IssuedBy('https://foo.bar', 'https://bar.foo'), new Constraint\PermittedFor('https://client2.bar'),
 new Constraint\ValidAt(new SystemClock()),
 new Constraint\SignedWith($signer, $key)
 ];
 
 $config->getValidator()->assert($token, ...$constraints);
  36. declare(strict_types=1); use Lcobucci\Clock\SystemClock;
 use Lcobucci\JWT\Validation\Constraint; /** @var \Lcobucci\JWT\Configuration $config */

    $config = require 'config.php'; $signer = $config->getSigner(); $key = $config->getVerificationKey();
 $token = $config->getParser()->parse('eyJ0eNiJ9 (...)'); $constraints = [ new Constraint\IssuedBy('https://foo.bar', 'https://bar.foo'), new Constraint\PermittedFor('https://client2.bar'),
 new Constraint\ValidAt(new SystemClock()),
 new Constraint\SignedWith($signer, $key)
 ];
 
 var_dump($config->getValidator()->validate($token, ...$constraints));