Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JWT - To authentication and beyond!

2,719 views

Published on

We use tokens to identify resources and try to ensure data security in insecure environments, however the management of these tokens can get quite complex. When we have distributed environments things are harder to deal with. Come to the magical world of JSON Web Tokens and make your life simpler!

Published in: Software

JWT - To authentication and beyond!

  1. 1. JWT To infinity
 & beyond! authentication Luís Cobucci
 @lcobucci https://goo.gl/gbd3H5
  2. 2. Tokens? https://goo.gl/C0LI6F
  3. 3. Browser Server DB
  4. 4. Browser Server 1. presents credentials POST /login 
 {
 "email": "aa@aa.com", "password": "amazing!"
 } DB
  5. 5. Browser Server DB 1. presents credentials 2. validates and starts a session
  6. 6. 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
  7. 7. 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
  8. 8. Browser Server DB 1. presents credentials 2. validates and starts a session 3. sends cookies on next requests GET / Cookie: PHPSESSIONID=ABC123
  9. 9. 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!
  10. 10. “ (…) 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
  11. 11. A mission http://goo.gl/RfVHNu
  12. 12. { "token": "abc123", "uid": 1, "expiration": "…",
 "scope": ["a", "b", "c"] }
  13. 13. { "token": "abc123", "uid": 1,
 "expiration": "…", "scope": ["a", "b", "c"] } { "token": "def123", "uid": 2,
 "expiration": "…", "scope": ["a", "b"] }
  14. 14. { "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"] }
  15. 15. { "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"] }
  16. 16. { "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"] }
  17. 17. Wonderful, right? http://goo.gl/9jQFkj
  18. 18. No! http://goo.gl/XHI1fw
  19. 19. We need
 SIMPLE !it
  20. 20. Luís Cobucci
 @lcobucci
  21. 21. JOSEJSON Object Signing and Encryption
  22. 22. JOSEJSON Object Signing and Encryption jws jwa jwt jwe jwk
  23. 23. eyJ0eXAiOiJKV1QiLCJhbGciOiJ IUzI1NiJ9.eyJ1c2VyIjp7ImlkI joxLCJuYW1lIjoiTHXDrXMgQ29i dWNjaSJ9fQ.
  24. 24. eyJ0eXAiOiJKV1QiLCJhbGciOiJ IUzI1NiJ9.eyJ1c2VyIjp7ImlkI joxLCJuYW1lIjoiTHXDrXMgQ29i dWNjaSJ9fQ. base64_encode()
  25. 25. eyJ0eXAiOiJKV1QiLCJhbGciOiJ IUzI1NiJ9.eyJ1c2VyIjp7ImlkI joxLCJuYW1lIjoiTHXDrXMgQ29i dWNjaSJ9fQ.
  26. 26. eyJ0eXAiOiJKV1QiLCJhbGciOiJ IUzI1NiJ9 . eyJ1c2VyIjp7ImlkIjoxLCJuYW1 lIjoiTHXDrXMgQ29idWNjaSJ9fQ .
  27. 27. Base64URL eyJ0eXAiOiJKV1QiLCJhbGciOiJ IUzI1NiJ9 . eyJ1c2VyIjp7ImlkIjoxLCJuYW1 lIjoiTHXDrXMgQ29idWNjaSJ9fQ .
  28. 28. + → -
 / → _
 = → (removed) TeSJWlQ/ S4YaOgK5tz7j+3KxBA g3HTONa9NP80R+9mY= TeSJWlQ_S4YaOgK5tz7 j-3KxBAg3HTONa9NP80 R-9mY
  29. 29. function base64url_encode(string $data): string { $data = base64_encode($data); 
 return rtrim(
 strtr($data, '+/', '-_'), '=' ); }
  30. 30. function base64url_decode(string $data): string {
 if ($remainder = strlen($data) % 4) { $data .= str_repeat('=', 4 - $remainder); } return base64_decode(
 strtr($data, '-_', '+/') ); }
  31. 31. eyJ0eXAiOiJKV1QiLCJhbGciOiJ IUzI1NiJ9 . eyJ1c2VyIjp7ImlkIjoxLCJuYW1 lIjoiTHXDrXMgQ29idWNjaSJ9fQ .
  32. 32. {
 "typ": "JWT",
 "alg": "none"
 } {
 "user": {
 "id": 1,
 "name": "Luís Cobucci"
 }
 }
  33. 33. The JSON! http://goo.gl/gH0hsx
  34. 34. 401
Unauthorised http://goo.gl/yyZ7oC
  35. 35. Client API DB
  36. 36. 1. presents credentials POST /auth 
 {
 "email": "aa@aa.com", "password": "amazing!"
 } Client API DB
  37. 37. 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
  38. 38. 1. presents credentials 2. validates and creates a token 201 Created 
 {
 "token": "…"
 } Client API DB
  39. 39. 1. presents credentials 2. validates and creates a token 3. sends the issued token GET / Authorization: … Client API DB
  40. 40. 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
  41. 41. How about OAuth2?
  42. 42. Sessions https://goo.gl/KNrl16
  43. 43. file Webserver Set-Cookie:
 SESSION_ID=123abc
  44. 44. file a Webserver Client SESSION_ID=123abc
  45. 45. file a Webserver 1 Client SESSION_ID=123abc file b Webserver 2 file c Webserver 3 Load balancer
  46. 46. Webserver 1 Client SESSION=eyJ0eXAiOiJKV1QiLCJhbGciO iJIUzI1NiJ9.eyJ1c2VyIjp7ImlkIjoxL CJuYW1lIjoiTHXDrXMgQ29idWNjaSJ9fQ .hv9V7gBBJPeWMbwFFmRP7clLuof7r9fV JzZbLZIxBTs Webserver 2 Webserver 3 Load balancer
  47. 47. ! 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
  48. 48. More!! https://goo.gl/gEjEMm
  49. 49. https://goo.gl/GB6YkQ Let’s investigate it
  50. 50. {
 "typ": "JWT",
 "alg": "none"
 } {
 "user": {
 "id": 1,
 "name": "Luís Cobucci"
 }
 }
  51. 51. headers {
 "typ": "JWT",
 "alg": "none"
 } {
 "user": {
 "id": 1,
 "name": "Luís Cobucci"
 }
 }
  52. 52. headers {
 "typ": "JWT",
 "alg": "none"
 } {
 "user": {
 "id": 1,
 "name": "Luís Cobucci"
 }
 } claims
  53. 53. Base64URL( )
 + "." + 
 Base64URL( )
 + "." headers claims
  54. 54. headers {
 "typ": "JWT",
 "alg": "none"
 } {
 "user": {
 "id": 1,
 "name": "Luís Cobucci"
 }
 } claims
  55. 55. eyJhbGciOiJub25lIiwidHlwIjo iSldUIn0
 .
 eyJ1c2VyIjp7ImlkIjoxLCJuYW1 lIjoiTHXDrXMgQ29idWNjaSJ9fQ
 .
  56. 56. Can you trust it? https://goo.gl/EeeIdu
  57. 57. Base64URL( )
 + "." + 
 Base64URL( ) headers claims payload
  58. 58. Base64URL( )
 + "." + 
 Base64URL( ) headers claims payload = alg( , )payload keysignature
  59. 59. Base64URL( )
 + "." + 
 Base64URL( )
 + "." + 
 Base64URL( ) headers claims signature
  60. 60. 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; }
  61. 61. What algorithms? https://goo.gl/qNTg3D
  62. 62. HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512 none HMAC RSA ECDSA RSASSA-PSS
  63. 63. HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512 none HMAC RSA ECDSA RSASSA-PSS
  64. 64. HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 PS256 PS384 PS512 none HMAC RSA ECDSA RSASSA-PSS
  65. 65. headers {
 "typ": "JWT",
 "alg": "HS256"
 } {
 "user": {
 "id": 1,
 "name": "Luís Cobucci"
 }
 } claims key Hello JWT+JWS!
  66. 66. eyJhbGciOiJub25lIiwidHlwIjo iSldUIn0
 .
 eyJ1c2VyIjp7ImlkIjoxLCJuYW1 lIjoiTHXDrXMgQ29idWNjaSJ9fQ
 .
 hv9V7gBBJPeWMbwFFmRP7clLuof 7r9fVJzZbLZIxBTs
  67. 67. How much data? https://goo.gl/eHFQwO
  68. 68. iat (NumericDate) exp (NumericDate) nbf (NumericDate) jti (String) iss (StringOrUri) aud (StringOrUri[]) sub (StringOrUri)
  69. 69. iat (NumericDate) exp (NumericDate) nbf (NumericDate) jti (String) iss (StringOrUri) aud (StringOrUri[]) sub (StringOrUri) case-sensitive
  70. 70. https://goo.gl/bkXMeq Revoke tokens?
  71. 71. PHP libraries! https://goo.gl/bGP8u8
  72. 72. declare(strict_types=1); require 'vendor/autoload.php'; use LcobucciJWTConfiguration; use LcobucciJWTSignerKey; use LcobucciJWTSignerHmacSha256; return Configuration::forSymmetricSigner( new Sha256(), new Key('my super secret key') );
  73. 73. declare(strict_types=1); require 'vendor/autoload.php'; use LcobucciJWTConfiguration; use LcobucciJWTSignerKey; use LcobucciJWTSignerRsaSha256; return Configuration::forAsymmetricSigner( new Sha256(), new Key('file://private.pem', 'testing'), new Key('file://public.pem') );
  74. 74. declare(strict_types=1); /** @var LcobucciJWTConfiguration $config */ $config = require 'config.php'; $signer = $config->getSigner(); $key = $config->getSigningKey(); $token = $config->createBuilder() ->withClaim('uid', 1) ->getToken($signer, $key);
  75. 75. declare(strict_types=1); /** @var LcobucciJWTConfiguration $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);
  76. 76. declare(strict_types=1); /** @var LcobucciJWTConfiguration $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);
  77. 77. declare(strict_types=1); /** @var LcobucciJWTConfiguration $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);
  78. 78. declare(strict_types=1); /** @var LcobucciJWTConfiguration $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);
  79. 79. eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9
 .
 eyJ1aWQiOjEsImp0aSI6ImFhMzk3YzA2ZDUwZmRhNjYyYWUwZGE4OTU2ODdmN zY3IiwiaXNzIjoiaHR0cHM6Ly9mb28uYmFyIiwiYXVkIjpbImh0dHBzOi8vY2 xpZW50MS5iYXIiLCJodHRwczovL2NsaWVudDIuYmFyIl0sImlhdCI6IjE0OTU xNzg5MDQuODY4ODc5IiwibmJmIjoiMTQ5NTE3OTIwNC44Njg4NzkiLCJleHAi OiIxNDk1MTgyNTA0Ljg2ODg3OSJ9
 .
 jwXzXjm8cU92yxP3XcENg_ZnDvW1MkRTzSoaAwOYCTlSdQ5rv- dCLn_7_XPLHSuiACt_aFTnB093GYTpJQKRnqIFPYteK2jVnQALXNPxntnp- v6SMiFBxofCaVSjgKTWdqkWB4agWrTR77HK_iKdFoZMIdpr8UUBJatkc_MCoD vDMtuDRXwIEBfjs9baICtBvTZyDD7iiMmF4F_qvp2mWd_Qy93gZCrePKAJsgY - sujg84iQFOs-6I3GjybzA0U0Y_bTmCmQHfhRUX5_gL21bZxBFef38OFKW73Vx ehBxM4Ok_nWRbGY7ehsMBshXkJQfp97TJ1cV35a9zyAVXC04A
  80. 80. {
 "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"
 }
  81. 81. declare(strict_types=1); /** @var LcobucciJWTConfiguration $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);
  82. 82. declare(strict_types=1); use LcobucciClockSystemClock;
 use LcobucciJWTValidationConstraint; /** @var LcobucciJWTConfiguration $config */ $config = require 'config.php'; $signer = $config->getSigner(); $key = $config->getVerificationKey();
 $token = $config->getParser()->parse('eyJ0eNiJ9 (...)'); $constraints = [ new ConstraintIssuedBy('https://foo.bar', 'https://bar.foo'), new ConstraintPermittedFor('https://client2.bar'),
 new ConstraintValidAt(new SystemClock()),
 new ConstraintSignedWith($signer, $key)
 ];
 
 $config->getValidator()->assert($token, ...$constraints);
  83. 83. declare(strict_types=1); use LcobucciClockSystemClock;
 use LcobucciJWTValidationConstraint; /** @var LcobucciJWTConfiguration $config */ $config = require 'config.php'; $signer = $config->getSigner(); $key = $config->getVerificationKey();
 $token = $config->getParser()->parse('eyJ0eNiJ9 (...)'); $constraints = [ new ConstraintIssuedBy('https://foo.bar', 'https://bar.foo'), new ConstraintPermittedFor('https://client2.bar'),
 new ConstraintValidAt(new SystemClock()),
 new ConstraintSignedWith($signer, $key)
 ];
 
 var_dump($config->getValidator()->validate($token, ...$constraints));
  84. 84. It’s up to you! https://goo.gl/vYG4zt
  85. 85. JWT To infinity
 & beyond! authentication Luís Cobucci
 @lcobucci https://goo.gl/gbd3H5
  86. 86. Thanks! @lcobucci

×