JWT
To infinity

& beyond!
authentication
Luís Cobucci

@lcobucci
https://goo.gl/gbd3H5
Tokens?
https://goo.gl/C0LI6F
Browser
Server DB
Browser
Server
1. presents credentials
POST /login


{

"email": "aa@aa.com",
"password": "amazing!"

}
DB
Browser
Server DB
1. presents credentials
2. validates and starts a session
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
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
Browser
Server DB
1. presents credentials
2. validates and starts a session
3. sends cookies on next requests
GET /
Cookie: PHPSESSIONID=ABC123
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!
“ (…) 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
A mission
http://goo.gl/RfVHNu
{
"token": "abc123", "uid": 1, "expiration": "…",

"scope": ["a", "b", "c"]
}
{
"token": "abc123", "uid": 1,

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

"expiration": "…", "scope": ["a", "b"]
}
{
"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": "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"]
}
{
"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"]
}
Wonderful, right?
http://goo.gl/9jQFkj
No!
http://goo.gl/XHI1fw
We need

SIMPLE !it
Luís Cobucci

@lcobucci
JOSEJSON Object Signing and Encryption
JOSEJSON Object Signing and Encryption
jws jwa
jwt jwe
jwk
eyJ0eXAiOiJKV1QiLCJhbGciOiJ
IUzI1NiJ9.eyJ1c2VyIjp7ImlkI
joxLCJuYW1lIjoiTHXDrXMgQ29i
dWNjaSJ9fQ.
eyJ0eXAiOiJKV1QiLCJhbGciOiJ
IUzI1NiJ9.eyJ1c2VyIjp7ImlkI
joxLCJuYW1lIjoiTHXDrXMgQ29i
dWNjaSJ9fQ.
base64_encode()
eyJ0eXAiOiJKV1QiLCJhbGciOiJ
IUzI1NiJ9.eyJ1c2VyIjp7ImlkI
joxLCJuYW1lIjoiTHXDrXMgQ29i
dWNjaSJ9fQ.
eyJ0eXAiOiJKV1QiLCJhbGciOiJ
IUzI1NiJ9
.
eyJ1c2VyIjp7ImlkIjoxLCJuYW1
lIjoiTHXDrXMgQ29idWNjaSJ9fQ
.
Base64URL
eyJ0eXAiOiJKV1QiLCJhbGciOiJ
IUzI1NiJ9
.
eyJ1c2VyIjp7ImlkIjoxLCJuYW1
lIjoiTHXDrXMgQ29idWNjaSJ9fQ
.
+ → -

/ → _

= → (removed)
TeSJWlQ/
S4YaOgK5tz7j+3KxBA
g3HTONa9NP80R+9mY=
TeSJWlQ_S4YaOgK5tz7
j-3KxBAg3HTONa9NP80
R-9mY
function base64url_encode(string $data): string {
$data = base64_encode($data);


return rtrim(

strtr($data, '+/', '-_'),
'='
);
}
function base64url_decode(string $data): string {

if ($remainder = strlen($data) % 4) {
$data .= str_repeat('=', 4 - $remainder);
}
return base64_decode(

strtr($data, '-_', '+/')
);
}
eyJ0eXAiOiJKV1QiLCJhbGciOiJ
IUzI1NiJ9
.
eyJ1c2VyIjp7ImlkIjoxLCJuYW1
lIjoiTHXDrXMgQ29idWNjaSJ9fQ
.
{

"typ": "JWT",

"alg": "none"

}
{

"user": {

"id": 1,

"name": "Luís Cobucci"

}

}
The JSON!
http://goo.gl/gH0hsx
401
Unauthorised
http://goo.gl/yyZ7oC
Client
API DB
1. presents credentials
POST /auth


{

"email": "aa@aa.com",
"password": "amazing!"

}
Client
API DB
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
1. presents credentials
2. validates and creates a token
201 Created


{

"token": "…"

}
Client
API DB
1. presents credentials
2. validates and creates a token
3. sends the issued token
GET /
Authorization: …
Client
API DB
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
How about OAuth2?
Sessions
https://goo.gl/KNrl16
file
Webserver
Set-Cookie:

SESSION_ID=123abc
file a
Webserver
Client
SESSION_ID=123abc
file a
Webserver 1
Client
SESSION_ID=123abc
file b
Webserver 2
file c
Webserver 3
Load balancer
Webserver 1
Client
SESSION=eyJ0eXAiOiJKV1QiLCJhbGciO
iJIUzI1NiJ9.eyJ1c2VyIjp7ImlkIjoxL
CJuYW1lIjoiTHXDrXMgQ29idWNjaSJ9fQ
.hv9V7gBBJPeWMbwFFmRP7clLuof7r9fV
JzZbLZIxBTs
Webserver 2 Webserver 3
Load balancer
!
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
More!!
https://goo.gl/gEjEMm
https://goo.gl/GB6YkQ
Let’s investigate it
{

"typ": "JWT",

"alg": "none"

}
{

"user": {

"id": 1,

"name": "Luís Cobucci"

}

}
headers
{

"typ": "JWT",

"alg": "none"

}
{

"user": {

"id": 1,

"name": "Luís Cobucci"

}

}
headers
{

"typ": "JWT",

"alg": "none"

}
{

"user": {

"id": 1,

"name": "Luís Cobucci"

}

}
claims
Base64URL( )

+ "." + 

Base64URL( )

+ "."
headers
claims
headers
{

"typ": "JWT",

"alg": "none"

}
{

"user": {

"id": 1,

"name": "Luís Cobucci"

}

}
claims
eyJhbGciOiJub25lIiwidHlwIjo
iSldUIn0

.

eyJ1c2VyIjp7ImlkIjoxLCJuYW1
lIjoiTHXDrXMgQ29idWNjaSJ9fQ

.
Can you trust it?
https://goo.gl/EeeIdu
Base64URL( )

+ "." + 

Base64URL( )
headers
claims
payload
Base64URL( )

+ "." + 

Base64URL( )
headers
claims
payload
= alg( , )payload keysignature
Base64URL( )

+ "." + 

Base64URL( )

+ "." + 

Base64URL( )
headers
claims
signature
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;
}
What
algorithms?
https://goo.gl/qNTg3D
HS256
HS384
HS512
RS256
RS384
RS512
ES256
ES384
ES512
PS256
PS384
PS512
none
HMAC
RSA
ECDSA
RSASSA-PSS
HS256
HS384
HS512
RS256
RS384
RS512
ES256
ES384
ES512
PS256
PS384
PS512
none
HMAC
RSA
ECDSA
RSASSA-PSS
HS256
HS384
HS512
RS256
RS384
RS512
ES256
ES384
ES512
PS256
PS384
PS512
none
HMAC
RSA
ECDSA
RSASSA-PSS
headers
{

"typ": "JWT",

"alg": "HS256"

}
{

"user": {

"id": 1,

"name": "Luís Cobucci"

}

}
claims
key
Hello JWT+JWS!
eyJhbGciOiJub25lIiwidHlwIjo
iSldUIn0

.

eyJ1c2VyIjp7ImlkIjoxLCJuYW1
lIjoiTHXDrXMgQ29idWNjaSJ9fQ

.

hv9V7gBBJPeWMbwFFmRP7clLuof
7r9fVJzZbLZIxBTs
How much data?
https://goo.gl/eHFQwO
iat (NumericDate)
exp (NumericDate)
nbf (NumericDate)
jti (String)
iss (StringOrUri)
aud (StringOrUri[])
sub (StringOrUri)
iat (NumericDate)
exp (NumericDate)
nbf (NumericDate)
jti (String)
iss (StringOrUri)
aud (StringOrUri[])
sub (StringOrUri)
case-sensitive
https://goo.gl/bkXMeq
Revoke tokens?
PHP libraries!
https://goo.gl/bGP8u8
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')
);
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')
);
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);
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);
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);
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);
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);
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9

.

eyJ1aWQiOjEsImp0aSI6ImFhMzk3YzA2ZDUwZmRhNjYyYWUwZGE4OTU2ODdmN
zY3IiwiaXNzIjoiaHR0cHM6Ly9mb28uYmFyIiwiYXVkIjpbImh0dHBzOi8vY2
xpZW50MS5iYXIiLCJodHRwczovL2NsaWVudDIuYmFyIl0sImlhdCI6IjE0OTU
xNzg5MDQuODY4ODc5IiwibmJmIjoiMTQ5NTE3OTIwNC44Njg4NzkiLCJleHAi
OiIxNDk1MTgyNTA0Ljg2ODg3OSJ9

.

jwXzXjm8cU92yxP3XcENg_ZnDvW1MkRTzSoaAwOYCTlSdQ5rv-
dCLn_7_XPLHSuiACt_aFTnB093GYTpJQKRnqIFPYteK2jVnQALXNPxntnp-
v6SMiFBxofCaVSjgKTWdqkWB4agWrTR77HK_iKdFoZMIdpr8UUBJatkc_MCoD
vDMtuDRXwIEBfjs9baICtBvTZyDD7iiMmF4F_qvp2mWd_Qy93gZCrePKAJsgY
-
sujg84iQFOs-6I3GjybzA0U0Y_bTmCmQHfhRUX5_gL21bZxBFef38OFKW73Vx
ehBxM4Ok_nWRbGY7ehsMBshXkJQfp97TJ1cV35a9zyAVXC04A
{

"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"

}
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);
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);
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));
It’s up to you!
https://goo.gl/vYG4zt
JWT
To infinity

& beyond!
authentication
Luís Cobucci

@lcobucci
https://goo.gl/gbd3H5
Thanks!
@lcobucci

JWT - To authentication and beyond!