SlideShare une entreprise Scribd logo
1  sur  43
Télécharger pour lire hors ligne
Autenticazione sessionless
delle API con JWT e Symfony
Chi sono
Marco Albarelli
Freelance
Full stack developer web e mobile
Sysadmin
PHP, javascript, Android, Go, java
https://www.adhocmobile.it
https://github.com/marcoalbarelli
Scenario
Un mondo fatto
di API
Ci sono più microservizi usati da
client di ogni genere:
JS, mobile, server, cli
Forniti da server di ogni genere:
nodejs, php, .Net, Java, ESB
Potenzialmente identità multiple
Condividere fra tutti la sessione
serverside diventa impraticabile
Un mondo fatto
di API
Un nuovo standard
Per permettere a sistemi diversi di
interagire serviva un nuovo
standard:
OpenID Connect
google lo usa in produzione https:
//developers.google.
com/identity/protocols/OpenIDConn
ect
Un mondo fatto
di API
Un nuovo standard
Obiettivo:
Passare da
Cookie: PHPSESSID
a
Authorization: Bearer mqZSaG...
Un mondo fatto
di API
Un nuovo standard:
OpenID Connect
Si basa su Oauth2.0
Usa dei token particolari: JWT
Interoperabile
Molto più semplice di SAML
Cos’è un
JSON WEB TOKEN
JWT, cos’è
Una convenzione: RFC 7519
Una stringa: 3 blocchi di testo
Base64 encoded uniti da un punto
Supporta JOSE: Json Object Signing
and Encryption
Composto di header, corpo e firma
Supporta “claims”
JWT, cos’è
Un esempio:
eyJhbGciOiJIUzI1NiIsInR5cCI6Ikp
XVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwi
bmFtZSI6IkpvaG4gRG9lIiwiYWRt
aW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDc
EfxjoYZgeFONFh7HgQ
http://jwt.io
JWT, cos’è
Che equivale a:
{ "alg": "HS256", "typ": "JWT" }
{ "sub": "1234567890", "name":
"John Doe", "admin": true }
hash_hmac( base64($header) . ”.” .
base64($body) , ”secret” )
“sub” “name” e “admin” sono claim
JWT, cos’è
Standard claims
iss: issuer
aud: audience
nbf: not before
exp: expiration
sub: subject
iat: issued at
jti: jwt id
typ: type
Cosa non vedremo oggi
Troppo poco
tempo per:
il flusso OpenID completo
Usare lo stack completo di
autenticazione di Symfony
Validare e usare token JWT cifrati
Cosa vedremo oggi
Abbastanza
tempo per:
Una ricetta quasi “standard” dal
cookbook di symfony
Ingredienti:
SimplePreAuthenticatorInterface
"firebase/php-jwt": "^3.0"
La ricetta
La ricetta
Repository WIP:
https://github.com/
marcoalbarelli/symfony2notes
La ricetta
Installiamo symfony come da
manuale
Aggiungiamo al composer.json:
"friendsofsymfony/user-bundle":
"2.0.x-dev",
"firebase/php-jwt": "^3.0"
composer update
La ricetta
Testiamo due scenari:
Richiesta con token non valido e ci
aspettiamo un codice 401
Richiesta con token valido e ci
aspettiamo un codice 200
Cosa stiamo per fare
Scriviamo il primo test:
public function testApiEndpointsAreInaccessibleWithAnInvalidJWTAuthorizationHeader($method,$route,$params){
$this->setupMocksWithoutExpectations();
$router = $this->container->get('router');
$uri = $router->generate($route,$params);
$this->client->setServerParameter('HTTP_Authorization',
'Bearer'.$this->createInvalidJWT($this->container->getParameter('secret')));
$this->client->request($method,$uri,$params);
$this->assertEquals(401,$this->client->getResponse()->getStatusCode());
}
Testiamo che la chiamata ottenga risposta (401)
Il token non è valido
Scriviamo il primo test:
public function testApiEndpointsAreInaccessibleWithAnInvalidJWTAuthorizationHeader($method,$route,$params){
$this->setupMocksWithoutExpectations();
$router = $this->container->get('router');
$uri = $router->generate($route,$params);
$this->client->setServerParameter('HTTP_Authorization',
'Bearer'.$this->createInvalidJWT($this->container->getParameter('secret')));
$this->client->request($method,$uri,$params);
$this->assertEquals(401,$this->client->getResponse()->getStatusCode());
}
Testiamo che la chiamata ottenga risposta (401)
Il token non è valido
$this->client->setServerParameter(
'HTTP_Authorization',
'Bearer'.$this->createInvalidJWT(
$this->container->getParameter('secret')
));
JWT Test doubles
public function createValidJWT($key,$role = 'ROLE_USER',$apiKey =
null)
{
$now = new DateTime('now');
$role = 'ROLE_USER';
if($apiKey == null){
$apiKey = md5(rand(0,10));
}
$token = array(
"iss" => "http://example.org",
"aud" => "http://example.com",
"iat" => $now->getTimestamp(),
"nbf" => $now->sub(new
DateInterval('P1D'))->getTimestamp(),
"role" => $role,
Constants::JWT_APIKEY_PARAMETER_NAME => $apiKey
);
return JWT::encode($token,$key);
}
public function createInvalidJWT($key,$role = 'ROLE_USER')
{
$now = new DateTime('now');
$role = 'ROLE_USER';
//Missing apikey and valid since tomorrow
$token = array(
"iss" => "http://example.org",
"aud" => "http://example.com",
"iat" => $now->getTimestamp(),
"nbf" => $now->add(new
DateInterval('P1D'))->getTimestamp(),
"role" => $role
);
return JWT::encode($token,$key);
}
JWT Test doubles
public function createValidJWT($key,$role = 'ROLE_USER',$apiKey =
null)
{
$now = new DateTime('now');
$role = 'ROLE_USER';
if($apiKey == null){
$apiKey = md5(rand(0,10));
}
$token = array(
"iss" => "http://example.org",
"aud" => "http://example.com",
"iat" => $now->getTimestamp(),
"nbf" => $now->sub(new
DateInterval('P1D'))->getTimestamp(),
"role" => $role,
Constants::JWT_APIKEY_PARAMETER_NAME => $apiKey
);
return JWT::encode($token,$key);
}
public function createInvalidJWT($key,$role = 'ROLE_USER')
{
$now = new DateTime('now');
$role = 'ROLE_USER';
//Missing apikey and valid since tomorrow
$token = array(
"iss" => "http://example.org",
"aud" => "http://example.com",
"iat" => $now->getTimestamp(),
"nbf" => $now->add(new
DateInterval('P1D'))->getTimestamp(),
"role" => $role
);
return JWT::encode($token,$key);
}
"nbf" => $now->add(
new DateInterval('P1D'))->getTimestamp()
);
Scriviamo il primo test:
CONTROLLER
/**
* @Route("/hello/{name}", name="api_hello")
*/
public function indexAction($name)
{
return new Response(json_encode(array('hello'=>$name)));
}
Come si vede per usare un autenticatore custom
non dobbiamo modificare il controller
Scriviamo il primo test:
Lanciamo i test adesso e siamo in
profondo rosso: dobbiamo
configurare un bel po’ di cose:
app/config/security.yml
security:
...
firewalls:
...
api_area:
pattern: ^/api/
provider: api_chain_provider
stateless: true
entry_point: marcoalbarelli.api_user_auth_entrypoint
anonymous: ~
simple_preauth:
authenticator: marcoalbarelli.api_user_authenticator
access_control:
- { path: ^/api/status, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Entry point
Authenticator vero e proprio
Due cose principali:
Scriviamo il primo test:
Lanciamo i test adesso e siamo in
profondo rosso: dobbiamo
configurare un bel po’ di cose:
app/config/security.yml
security:
...
firewalls:
...
api_area:
pattern: ^/api/
provider: api_chain_provider
stateless: true
entry_point: marcoalbarelli.api_user_auth_entrypoint
anonymous: ~
simple_preauth:
authenticator: marcoalbarelli.api_user_authenticator
access_control:
- { path: ^/api/status, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Entry point
Authenticator vero e proprio
Due cose principali:
authenticator: marcoalbarelli.
api_user_authenticator
...
marcoalbarelli.api_user_authenticator:
class: MarcoalbarelliAPIBundleServiceAPIUserAuthenticator
arguments:
- @marcoalbarelli.api_user_provider
- @marcoalbarelli.jwt_checker
Entry point:
public function testAuthEntrypointGives401ErrorForMissingJWT(){
$authException = new AuthenticationException("missing JWT");
$request = new Request();
$service = $this->container->get('marcoalbarelli.
api_user_auth_entrypoint');
$response = $service->start($request,$authException);
$this->assertTrue($response instanceof Response);
$this->assertEquals('application/json',$response->headers->get
('Content-Type'));
$this->assertEquals('OpenID realm="api_area"',$response-
>headers->get('WWW-Authenticate'));
}
/**
* Starts the authentication scheme.
*
* @param Request $request The request that resulted in an
AuthenticationException
* @param AuthenticationException $authException The exception that
started the authentication process
*
* @return Response
*/
public function start(Request $request, AuthenticationException
$authException = null)
{
$content = array('success'=>false);
$response = new Response(json_encode($content),401);
$response->headers->set('Content-Type','application/json');
$response->headers->set('WWW-Authenticate','OpenID realm="
api_area"'); //TODO: retrieve the firewall name dynamically
return $response;
}
SimplePreAuthenticatorInterface:
/**
* @expectedException Exception
*/
public function testAuthenticatorThrowsExceptionIfRequestIsInvalid(){
$jwt = $this->createInvalidJWT($this->container->getParameter
('secret'));
$request = new Request();
$request->headers->add(array('Authorization'=> Constants::
JWT_BEARER_PREFIX .$jwt));
$service = $this->container->get('marcoalbarelli.
api_user_authenticator');
$service->createToken($request,'pippo');
}
public function createToken(Request $request, $providerKey)
{
$authorizationHeader = $request->headers->get('Authorization');
…
$encodedJWT = $authorizationHeader[1];
try {
$jwt = $this->jwtCheckerService->
decodeToken($encodedJWT);
} catch (Exception $exception){
throw new AuthenticationException
($exception->
getMessage());
}
$user = $this->userProvider->findUserByAPIKey($jwt->$apiKeyName);
...
$token = new PreAuthenticatedToken($user,$encodedJWT,$providerKey);
return $token;
}
SimplePreAuthenticatorInterface:
/**
* @expectedException Exception
*/
public function testAuthenticatorThrowsExceptionIfRequestIsInvalid(){
$jwt = $this->createInvalidJWT($this->container->getParameter
('secret'));
$request = new Request();
$request->headers->add(array('Authorization'=> Constants::
JWT_BEARER_PREFIX .$jwt));
$service = $this->container->get('marcoalbarelli.
api_user_authenticator');
$service->createToken($request,'pippo');
}
public function createToken(Request $request, $providerKey)
{
$authorizationHeader = $request->headers->get('Authorization');
…
$encodedJWT = $authorizationHeader[1];
try {
$jwt = $this->jwtCheckerService->
decodeToken($encodedJWT);
} catch (Exception $exception){
throw new AuthenticationException
($exception->
getMessage());
}
$user = $this->userProvider->findUserByAPIKey($jwt->$apiKeyName);
...
$token = new PreAuthenticatedToken($user,$encodedJWT,$providerKey);
return $token;
}
Il fulcro di tutto:
$jwt = $this->jwtCheckerService->
decodeToken($encodedJWT);
JWT Checker
/**
* @expectedException Exception
*/
public function testServiceThrowsExceptionForInvalidJWTToken(){
$key = $key = $this->container->getParameter('secret');
$token = $this->createInvalidJWT($key);
$service = $this->container->get('marcoalbarelli.jwt_checker');
$service->decodeToken($token);
}
//TODO: creare un dataprovider che copra
esplicitamente tutti i casi di invalidità
/**
* @var string $secret The secret for this deployment (from parameters.
yml)
*/
private $secret;
/**
* @var array $algs The algs for JWT signing (from parameters.yml)
*/
private $algs;
public function __construct($secret, $algs)
{
$this->secret = $secret;
$this->algs = $algs;
}
public function decodeToken($token)
{
return JWT::decode($token, $this->secret,
$this->algs);
}
JWT Checker
/**
* @expectedException Exception
*/
public function testServiceThrowsExceptionForInvalidJWTToken(){
$key = $key = $this->container->getParameter('secret');
$token = $this->createInvalidJWT($key);
$service = $this->container->get('marcoalbarelli.jwt_checker');
$service->decodeToken($token);
}
//TODO: creare un dataprovider che copra
esplicitamente tutti i casi di invalidità
/**
* @var string $secret The secret for this deployment (from parameters.
yml)
*/
private $secret;
/**
* @var array $algs The algs for JWT signing (from parameters.yml)
*/
private $algs;
public function __construct($secret, $algs)
{
$this->secret = $secret;
$this->algs = $algs;
}
public function decodeToken($token)
{
return JWT::decode($token, $this->secret,
$this->algs);
}
La prima implementazione:
solo correttezza formale
return JWT::decode($token, $this-
>secret, $this->algs);
Scriviamo il secondo test:
public function testApiEndpointsAreAccessibleWithAValidJWTAuthorizationHeader($method,$route,$params){
$this->setupMocks();
$router = $this->container->get('router');
$uri = $router->generate($route,$params);
$this->client->setServerParameter('HTTP_Authorization',
'Bearer '.$this->createValidJWT($this->container->getParameter('secret')));
$this->client->request($method,$uri,$params);
$this->assertEquals(200,$this->client->getResponse()->getStatusCode());
}
Testiamo che la chiamata ottenga risposta
Praticamente identico al precedente, tranne che per il token (stavolta valido)
SimplePreAuthenticatorInterface:
public function
testAuthenticatorCreatesValidTokenIfRequestIsValidAnUserIsPresent(){
$jwt = $this->createValidJWT($this->container->getParameter
('secret'));
$request = new Request();
$request->headers->add(array('Authorization'=> Constants::
JWT_BEARER_PREFIX .$jwt));
$this->container->set('marcoalbarelli.api_user_provider',$this-
>getMockedUserProvider());
$service = $this->container->get('marcoalbarelli.
api_user_authenticator');
$preauthenticatedToken = $service->createToken($request,'pippo');
$this->assertNotNull($preauthenticatedToken);
$this->assertEquals($preauthenticatedToken->getCredentials(),
$jwt);
}
public function createToken(Request $request, $providerKey)
{
….
//Tutto ok
$token = new PreAuthenticatedToken($user,
$encodedJWT,$providerKey);
return $token;
}
Il cuore dell’autenticazione
try {
$jwt = $this->jwtCheckerService->decodeToken($encodedJWT);
} catch (Exception $exception){
throw new AuthenticationException($exception->getMessage());
}
if( !isset($jwt->$apiKeyName)){
throw new BadCredentialsException('Invalid JWT');
}
$user = $this->userProvider->findUserByAPIKey($jwt->$apiKeyName);
if($user == null){
throw new UsernameNotFoundException("Invalid User");
}
…
$token = new PreAuthenticatedToken($user,$encodedJWT,$providerKey);
return $token;
Qui possiamo aggiungere una miriade
di controlli sia sul nostro sistema che
su altri (grazie all’interoperabilità
offerta da JWT)
Prossimi passi?
Prossimi passi
Implementazione di tutto il flusso
OpenID Connect
Implementazione dei token JWT
cifrati
Implementazione di un AP con
symfony (soon on a github near you)
Conclusioni
Conclusioni
Symfony + JWT
Autenticazione API
Abbiamo visto rapidamente come
creare un autenticatore per delle API
che non si appoggia ai cookie di
sessione
Abbiamo visto come sia semplice
farlo con approccio TDD, essenziale
in ambito di sicurezza
Abbiamo iniziato ad usare un nuovo
standard che ci renderà più facile
integrarci con OpenID Connect
Conclusioni
Symfony + JWT
Autenticazione API
Domande?
Grazie

Contenu connexe

Tendances

Php mysql e cms
Php mysql e cmsPhp mysql e cms
Php mysql e cmsorestJump
 
Mantenere una distribuzione Drupal attraverso test coverage: Paddle case study
Mantenere una distribuzione Drupal attraverso test coverage: Paddle case studyMantenere una distribuzione Drupal attraverso test coverage: Paddle case study
Mantenere una distribuzione Drupal attraverso test coverage: Paddle case studyDrupalDay
 
Drupal diventa un CMF e WordPress che fa? Slide WordCamp Milano 2019
Drupal diventa un CMF e WordPress che fa? Slide WordCamp Milano 2019Drupal diventa un CMF e WordPress che fa? Slide WordCamp Milano 2019
Drupal diventa un CMF e WordPress che fa? Slide WordCamp Milano 2019Matteo Enna
 
jQuery e i suoi plugin
jQuery e i suoi pluginjQuery e i suoi plugin
jQuery e i suoi pluginPasquale Puzio
 

Tendances (6)

Php mysql e cms
Php mysql e cmsPhp mysql e cms
Php mysql e cms
 
Mantenere una distribuzione Drupal attraverso test coverage: Paddle case study
Mantenere una distribuzione Drupal attraverso test coverage: Paddle case studyMantenere una distribuzione Drupal attraverso test coverage: Paddle case study
Mantenere una distribuzione Drupal attraverso test coverage: Paddle case study
 
Drupal diventa un CMF e WordPress che fa? Slide WordCamp Milano 2019
Drupal diventa un CMF e WordPress che fa? Slide WordCamp Milano 2019Drupal diventa un CMF e WordPress che fa? Slide WordCamp Milano 2019
Drupal diventa un CMF e WordPress che fa? Slide WordCamp Milano 2019
 
Introduzione a jQuery
Introduzione a jQueryIntroduzione a jQuery
Introduzione a jQuery
 
Introduzione a node.js
Introduzione a node.jsIntroduzione a node.js
Introduzione a node.js
 
jQuery e i suoi plugin
jQuery e i suoi pluginjQuery e i suoi plugin
jQuery e i suoi plugin
 

En vedette

Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreRyan Weaver
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Fabien Potencier
 
Building a documented RESTful API in just a few hours with Symfony
Building a documented RESTful API in just a few hours with SymfonyBuilding a documented RESTful API in just a few hours with Symfony
Building a documented RESTful API in just a few hours with Symfonyolrandir
 
Service approach for development REST API in Symfony2
Service approach for development REST API in Symfony2Service approach for development REST API in Symfony2
Service approach for development REST API in Symfony2Sumy PHP User Grpoup
 
Service approach for development Rest API in Symfony2
Service approach for development Rest API in Symfony2Service approach for development Rest API in Symfony2
Service approach for development Rest API in Symfony2Sumy PHP User Grpoup
 
A high profile project with Symfony and API Platform: beIN SPORTS
A high profile project with Symfony and API Platform: beIN SPORTSA high profile project with Symfony and API Platform: beIN SPORTS
A high profile project with Symfony and API Platform: beIN SPORTSSmile I.T is open
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Leonardo Proietti
 
Présentation sur l'accessibilité numérique / Evènement université de Lille 3
Présentation sur l'accessibilité numérique / Evènement université de Lille 3 Présentation sur l'accessibilité numérique / Evènement université de Lille 3
Présentation sur l'accessibilité numérique / Evènement université de Lille 3 Smile I.T is open
 
Symfony in microservice architecture
Symfony in microservice architectureSymfony in microservice architecture
Symfony in microservice architectureDaniele D'Angeli
 
Creating hypermedia APIs in a few minutes using the API Platform framework
Creating hypermedia APIs in a few minutes using the API Platform frameworkCreating hypermedia APIs in a few minutes using the API Platform framework
Creating hypermedia APIs in a few minutes using the API Platform frameworkLes-Tilleuls.coop
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Ryan Weaver
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)Javier Eguiluz
 
Symfony tips and tricks
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricksJavier Eguiluz
 

En vedette (15)

Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
 
Building a documented RESTful API in just a few hours with Symfony
Building a documented RESTful API in just a few hours with SymfonyBuilding a documented RESTful API in just a few hours with Symfony
Building a documented RESTful API in just a few hours with Symfony
 
Http and REST APIs.
Http and REST APIs.Http and REST APIs.
Http and REST APIs.
 
Service approach for development REST API in Symfony2
Service approach for development REST API in Symfony2Service approach for development REST API in Symfony2
Service approach for development REST API in Symfony2
 
Service approach for development Rest API in Symfony2
Service approach for development Rest API in Symfony2Service approach for development Rest API in Symfony2
Service approach for development Rest API in Symfony2
 
A high profile project with Symfony and API Platform: beIN SPORTS
A high profile project with Symfony and API Platform: beIN SPORTSA high profile project with Symfony and API Platform: beIN SPORTS
A high profile project with Symfony and API Platform: beIN SPORTS
 
Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5Rich domain model with symfony 2.5 and doctrine 2.5
Rich domain model with symfony 2.5 and doctrine 2.5
 
Présentation sur l'accessibilité numérique / Evènement université de Lille 3
Présentation sur l'accessibilité numérique / Evènement université de Lille 3 Présentation sur l'accessibilité numérique / Evènement université de Lille 3
Présentation sur l'accessibilité numérique / Evènement université de Lille 3
 
30 Symfony Best Practices
30 Symfony Best Practices30 Symfony Best Practices
30 Symfony Best Practices
 
Symfony in microservice architecture
Symfony in microservice architectureSymfony in microservice architecture
Symfony in microservice architecture
 
Creating hypermedia APIs in a few minutes using the API Platform framework
Creating hypermedia APIs in a few minutes using the API Platform frameworkCreating hypermedia APIs in a few minutes using the API Platform framework
Creating hypermedia APIs in a few minutes using the API Platform framework
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
 
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)New Symfony Tips & Tricks (SymfonyCon Paris 2015)
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
 
Symfony tips and tricks
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricks
 

Similaire à Autenticazione delle api con jwt e symfony (Italian)

Creazione di una REST API in Liferay
Creazione di una REST API in LiferayCreazione di una REST API in Liferay
Creazione di una REST API in LiferayNunzio Mastrapasqua
 
Introduzione a Node.js
Introduzione a Node.jsIntroduzione a Node.js
Introduzione a Node.jsMichele Capra
 
L'impatto dei Servizi Applicativi
L'impatto dei Servizi ApplicativiL'impatto dei Servizi Applicativi
L'impatto dei Servizi Applicativimichelemanzotti
 
Web Api – The HTTP Way
Web Api – The HTTP WayWeb Api – The HTTP Way
Web Api – The HTTP WayLuca Milan
 
ASP.NET Core - dove siamo arrivati
ASP.NET Core - dove siamo arrivatiASP.NET Core - dove siamo arrivati
ASP.NET Core - dove siamo arrivatiAndrea Dottor
 
Primo Incontro Con Scala
Primo Incontro Con ScalaPrimo Incontro Con Scala
Primo Incontro Con ScalaFranco Lombardo
 
Dominare il codice legacy
Dominare il codice legacyDominare il codice legacy
Dominare il codice legacyTommaso Torti
 
Non Conventional Android Programming (Italiano)
Non Conventional Android Programming (Italiano)Non Conventional Android Programming (Italiano)
Non Conventional Android Programming (Italiano)Davide Cerbo
 
RESTful APIs (ITA) - /w WebMachine
RESTful APIs (ITA) - /w WebMachineRESTful APIs (ITA) - /w WebMachine
RESTful APIs (ITA) - /w WebMachineGiancarlo Valente
 
High specialized vm on open stack cloud
High specialized vm on open stack cloudHigh specialized vm on open stack cloud
High specialized vm on open stack cloudGabriele Baldoni
 
Lezione 7: Remote Method Invocation e SSL
Lezione 7: Remote Method Invocation e SSLLezione 7: Remote Method Invocation e SSL
Lezione 7: Remote Method Invocation e SSLAndrea Della Corte
 
AngularJS On Rails by Daniele Spinosa
AngularJS On Rails by Daniele SpinosaAngularJS On Rails by Daniele Spinosa
AngularJS On Rails by Daniele SpinosaCommit University
 
ASP.NET MVC3 - Tutti i compiti del Controller
ASP.NET MVC3 - Tutti i compiti del ControllerASP.NET MVC3 - Tutti i compiti del Controller
ASP.NET MVC3 - Tutti i compiti del ControllerManuel Scapolan
 
Lezione 6: Remote Method Invocation
Lezione 6: Remote Method InvocationLezione 6: Remote Method Invocation
Lezione 6: Remote Method InvocationAndrea Della Corte
 
Le novita di visual studio 2012
Le novita di visual studio 2012Le novita di visual studio 2012
Le novita di visual studio 2012Crismer La Pignola
 

Similaire à Autenticazione delle api con jwt e symfony (Italian) (20)

Hexagonal architecture ita
Hexagonal architecture itaHexagonal architecture ita
Hexagonal architecture ita
 
Creazione di una REST API in Liferay
Creazione di una REST API in LiferayCreazione di una REST API in Liferay
Creazione di una REST API in Liferay
 
Yagwto
YagwtoYagwto
Yagwto
 
Introduzione a Node.js
Introduzione a Node.jsIntroduzione a Node.js
Introduzione a Node.js
 
Java lezione 14
Java lezione 14Java lezione 14
Java lezione 14
 
Novità di Asp.Net 4.0
Novità di Asp.Net 4.0Novità di Asp.Net 4.0
Novità di Asp.Net 4.0
 
L'impatto dei Servizi Applicativi
L'impatto dei Servizi ApplicativiL'impatto dei Servizi Applicativi
L'impatto dei Servizi Applicativi
 
Web Api – The HTTP Way
Web Api – The HTTP WayWeb Api – The HTTP Way
Web Api – The HTTP Way
 
ASP.NET Core - dove siamo arrivati
ASP.NET Core - dove siamo arrivatiASP.NET Core - dove siamo arrivati
ASP.NET Core - dove siamo arrivati
 
Primo Incontro Con Scala
Primo Incontro Con ScalaPrimo Incontro Con Scala
Primo Incontro Con Scala
 
Dominare il codice legacy
Dominare il codice legacyDominare il codice legacy
Dominare il codice legacy
 
Non Conventional Android Programming (Italiano)
Non Conventional Android Programming (Italiano)Non Conventional Android Programming (Italiano)
Non Conventional Android Programming (Italiano)
 
RESTful APIs (ITA) - /w WebMachine
RESTful APIs (ITA) - /w WebMachineRESTful APIs (ITA) - /w WebMachine
RESTful APIs (ITA) - /w WebMachine
 
High specialized vm on open stack cloud
High specialized vm on open stack cloudHigh specialized vm on open stack cloud
High specialized vm on open stack cloud
 
Lezione 7: Remote Method Invocation e SSL
Lezione 7: Remote Method Invocation e SSLLezione 7: Remote Method Invocation e SSL
Lezione 7: Remote Method Invocation e SSL
 
AngularJS On Rails by Daniele Spinosa
AngularJS On Rails by Daniele SpinosaAngularJS On Rails by Daniele Spinosa
AngularJS On Rails by Daniele Spinosa
 
ASP.NET MVC3 - Tutti i compiti del Controller
ASP.NET MVC3 - Tutti i compiti del ControllerASP.NET MVC3 - Tutti i compiti del Controller
ASP.NET MVC3 - Tutti i compiti del Controller
 
Lezione 6: Remote Method Invocation
Lezione 6: Remote Method InvocationLezione 6: Remote Method Invocation
Lezione 6: Remote Method Invocation
 
Le novita di visual studio 2012
Le novita di visual studio 2012Le novita di visual studio 2012
Le novita di visual studio 2012
 
Html5 e PHP
Html5 e PHPHtml5 e PHP
Html5 e PHP
 

Autenticazione delle api con jwt e symfony (Italian)

  • 3. Marco Albarelli Freelance Full stack developer web e mobile Sysadmin PHP, javascript, Android, Go, java https://www.adhocmobile.it https://github.com/marcoalbarelli
  • 5.
  • 6. Un mondo fatto di API Ci sono più microservizi usati da client di ogni genere: JS, mobile, server, cli Forniti da server di ogni genere: nodejs, php, .Net, Java, ESB Potenzialmente identità multiple Condividere fra tutti la sessione serverside diventa impraticabile
  • 7. Un mondo fatto di API Un nuovo standard Per permettere a sistemi diversi di interagire serviva un nuovo standard: OpenID Connect google lo usa in produzione https: //developers.google. com/identity/protocols/OpenIDConn ect
  • 8. Un mondo fatto di API Un nuovo standard Obiettivo: Passare da Cookie: PHPSESSID a Authorization: Bearer mqZSaG...
  • 9. Un mondo fatto di API Un nuovo standard: OpenID Connect Si basa su Oauth2.0 Usa dei token particolari: JWT Interoperabile Molto più semplice di SAML
  • 11. JWT, cos’è Una convenzione: RFC 7519 Una stringa: 3 blocchi di testo Base64 encoded uniti da un punto Supporta JOSE: Json Object Signing and Encryption Composto di header, corpo e firma Supporta “claims”
  • 13. JWT, cos’è Che equivale a: { "alg": "HS256", "typ": "JWT" } { "sub": "1234567890", "name": "John Doe", "admin": true } hash_hmac( base64($header) . ”.” . base64($body) , ”secret” ) “sub” “name” e “admin” sono claim
  • 14. JWT, cos’è Standard claims iss: issuer aud: audience nbf: not before exp: expiration sub: subject iat: issued at jti: jwt id typ: type
  • 16. Troppo poco tempo per: il flusso OpenID completo Usare lo stack completo di autenticazione di Symfony Validare e usare token JWT cifrati
  • 18. Abbastanza tempo per: Una ricetta quasi “standard” dal cookbook di symfony Ingredienti: SimplePreAuthenticatorInterface "firebase/php-jwt": "^3.0"
  • 21. La ricetta Installiamo symfony come da manuale Aggiungiamo al composer.json: "friendsofsymfony/user-bundle": "2.0.x-dev", "firebase/php-jwt": "^3.0" composer update
  • 22. La ricetta Testiamo due scenari: Richiesta con token non valido e ci aspettiamo un codice 401 Richiesta con token valido e ci aspettiamo un codice 200 Cosa stiamo per fare
  • 23. Scriviamo il primo test: public function testApiEndpointsAreInaccessibleWithAnInvalidJWTAuthorizationHeader($method,$route,$params){ $this->setupMocksWithoutExpectations(); $router = $this->container->get('router'); $uri = $router->generate($route,$params); $this->client->setServerParameter('HTTP_Authorization', 'Bearer'.$this->createInvalidJWT($this->container->getParameter('secret'))); $this->client->request($method,$uri,$params); $this->assertEquals(401,$this->client->getResponse()->getStatusCode()); } Testiamo che la chiamata ottenga risposta (401) Il token non è valido
  • 24. Scriviamo il primo test: public function testApiEndpointsAreInaccessibleWithAnInvalidJWTAuthorizationHeader($method,$route,$params){ $this->setupMocksWithoutExpectations(); $router = $this->container->get('router'); $uri = $router->generate($route,$params); $this->client->setServerParameter('HTTP_Authorization', 'Bearer'.$this->createInvalidJWT($this->container->getParameter('secret'))); $this->client->request($method,$uri,$params); $this->assertEquals(401,$this->client->getResponse()->getStatusCode()); } Testiamo che la chiamata ottenga risposta (401) Il token non è valido $this->client->setServerParameter( 'HTTP_Authorization', 'Bearer'.$this->createInvalidJWT( $this->container->getParameter('secret') ));
  • 25. JWT Test doubles public function createValidJWT($key,$role = 'ROLE_USER',$apiKey = null) { $now = new DateTime('now'); $role = 'ROLE_USER'; if($apiKey == null){ $apiKey = md5(rand(0,10)); } $token = array( "iss" => "http://example.org", "aud" => "http://example.com", "iat" => $now->getTimestamp(), "nbf" => $now->sub(new DateInterval('P1D'))->getTimestamp(), "role" => $role, Constants::JWT_APIKEY_PARAMETER_NAME => $apiKey ); return JWT::encode($token,$key); } public function createInvalidJWT($key,$role = 'ROLE_USER') { $now = new DateTime('now'); $role = 'ROLE_USER'; //Missing apikey and valid since tomorrow $token = array( "iss" => "http://example.org", "aud" => "http://example.com", "iat" => $now->getTimestamp(), "nbf" => $now->add(new DateInterval('P1D'))->getTimestamp(), "role" => $role ); return JWT::encode($token,$key); }
  • 26. JWT Test doubles public function createValidJWT($key,$role = 'ROLE_USER',$apiKey = null) { $now = new DateTime('now'); $role = 'ROLE_USER'; if($apiKey == null){ $apiKey = md5(rand(0,10)); } $token = array( "iss" => "http://example.org", "aud" => "http://example.com", "iat" => $now->getTimestamp(), "nbf" => $now->sub(new DateInterval('P1D'))->getTimestamp(), "role" => $role, Constants::JWT_APIKEY_PARAMETER_NAME => $apiKey ); return JWT::encode($token,$key); } public function createInvalidJWT($key,$role = 'ROLE_USER') { $now = new DateTime('now'); $role = 'ROLE_USER'; //Missing apikey and valid since tomorrow $token = array( "iss" => "http://example.org", "aud" => "http://example.com", "iat" => $now->getTimestamp(), "nbf" => $now->add(new DateInterval('P1D'))->getTimestamp(), "role" => $role ); return JWT::encode($token,$key); } "nbf" => $now->add( new DateInterval('P1D'))->getTimestamp() );
  • 27. Scriviamo il primo test: CONTROLLER /** * @Route("/hello/{name}", name="api_hello") */ public function indexAction($name) { return new Response(json_encode(array('hello'=>$name))); } Come si vede per usare un autenticatore custom non dobbiamo modificare il controller
  • 28. Scriviamo il primo test: Lanciamo i test adesso e siamo in profondo rosso: dobbiamo configurare un bel po’ di cose: app/config/security.yml security: ... firewalls: ... api_area: pattern: ^/api/ provider: api_chain_provider stateless: true entry_point: marcoalbarelli.api_user_auth_entrypoint anonymous: ~ simple_preauth: authenticator: marcoalbarelli.api_user_authenticator access_control: - { path: ^/api/status, roles: IS_AUTHENTICATED_ANONYMOUSLY } Entry point Authenticator vero e proprio Due cose principali:
  • 29. Scriviamo il primo test: Lanciamo i test adesso e siamo in profondo rosso: dobbiamo configurare un bel po’ di cose: app/config/security.yml security: ... firewalls: ... api_area: pattern: ^/api/ provider: api_chain_provider stateless: true entry_point: marcoalbarelli.api_user_auth_entrypoint anonymous: ~ simple_preauth: authenticator: marcoalbarelli.api_user_authenticator access_control: - { path: ^/api/status, roles: IS_AUTHENTICATED_ANONYMOUSLY } Entry point Authenticator vero e proprio Due cose principali: authenticator: marcoalbarelli. api_user_authenticator ... marcoalbarelli.api_user_authenticator: class: MarcoalbarelliAPIBundleServiceAPIUserAuthenticator arguments: - @marcoalbarelli.api_user_provider - @marcoalbarelli.jwt_checker
  • 30. Entry point: public function testAuthEntrypointGives401ErrorForMissingJWT(){ $authException = new AuthenticationException("missing JWT"); $request = new Request(); $service = $this->container->get('marcoalbarelli. api_user_auth_entrypoint'); $response = $service->start($request,$authException); $this->assertTrue($response instanceof Response); $this->assertEquals('application/json',$response->headers->get ('Content-Type')); $this->assertEquals('OpenID realm="api_area"',$response- >headers->get('WWW-Authenticate')); } /** * Starts the authentication scheme. * * @param Request $request The request that resulted in an AuthenticationException * @param AuthenticationException $authException The exception that started the authentication process * * @return Response */ public function start(Request $request, AuthenticationException $authException = null) { $content = array('success'=>false); $response = new Response(json_encode($content),401); $response->headers->set('Content-Type','application/json'); $response->headers->set('WWW-Authenticate','OpenID realm=" api_area"'); //TODO: retrieve the firewall name dynamically return $response; }
  • 31. SimplePreAuthenticatorInterface: /** * @expectedException Exception */ public function testAuthenticatorThrowsExceptionIfRequestIsInvalid(){ $jwt = $this->createInvalidJWT($this->container->getParameter ('secret')); $request = new Request(); $request->headers->add(array('Authorization'=> Constants:: JWT_BEARER_PREFIX .$jwt)); $service = $this->container->get('marcoalbarelli. api_user_authenticator'); $service->createToken($request,'pippo'); } public function createToken(Request $request, $providerKey) { $authorizationHeader = $request->headers->get('Authorization'); … $encodedJWT = $authorizationHeader[1]; try { $jwt = $this->jwtCheckerService-> decodeToken($encodedJWT); } catch (Exception $exception){ throw new AuthenticationException ($exception-> getMessage()); } $user = $this->userProvider->findUserByAPIKey($jwt->$apiKeyName); ... $token = new PreAuthenticatedToken($user,$encodedJWT,$providerKey); return $token; }
  • 32. SimplePreAuthenticatorInterface: /** * @expectedException Exception */ public function testAuthenticatorThrowsExceptionIfRequestIsInvalid(){ $jwt = $this->createInvalidJWT($this->container->getParameter ('secret')); $request = new Request(); $request->headers->add(array('Authorization'=> Constants:: JWT_BEARER_PREFIX .$jwt)); $service = $this->container->get('marcoalbarelli. api_user_authenticator'); $service->createToken($request,'pippo'); } public function createToken(Request $request, $providerKey) { $authorizationHeader = $request->headers->get('Authorization'); … $encodedJWT = $authorizationHeader[1]; try { $jwt = $this->jwtCheckerService-> decodeToken($encodedJWT); } catch (Exception $exception){ throw new AuthenticationException ($exception-> getMessage()); } $user = $this->userProvider->findUserByAPIKey($jwt->$apiKeyName); ... $token = new PreAuthenticatedToken($user,$encodedJWT,$providerKey); return $token; } Il fulcro di tutto: $jwt = $this->jwtCheckerService-> decodeToken($encodedJWT);
  • 33. JWT Checker /** * @expectedException Exception */ public function testServiceThrowsExceptionForInvalidJWTToken(){ $key = $key = $this->container->getParameter('secret'); $token = $this->createInvalidJWT($key); $service = $this->container->get('marcoalbarelli.jwt_checker'); $service->decodeToken($token); } //TODO: creare un dataprovider che copra esplicitamente tutti i casi di invalidità /** * @var string $secret The secret for this deployment (from parameters. yml) */ private $secret; /** * @var array $algs The algs for JWT signing (from parameters.yml) */ private $algs; public function __construct($secret, $algs) { $this->secret = $secret; $this->algs = $algs; } public function decodeToken($token) { return JWT::decode($token, $this->secret, $this->algs); }
  • 34. JWT Checker /** * @expectedException Exception */ public function testServiceThrowsExceptionForInvalidJWTToken(){ $key = $key = $this->container->getParameter('secret'); $token = $this->createInvalidJWT($key); $service = $this->container->get('marcoalbarelli.jwt_checker'); $service->decodeToken($token); } //TODO: creare un dataprovider che copra esplicitamente tutti i casi di invalidità /** * @var string $secret The secret for this deployment (from parameters. yml) */ private $secret; /** * @var array $algs The algs for JWT signing (from parameters.yml) */ private $algs; public function __construct($secret, $algs) { $this->secret = $secret; $this->algs = $algs; } public function decodeToken($token) { return JWT::decode($token, $this->secret, $this->algs); } La prima implementazione: solo correttezza formale return JWT::decode($token, $this- >secret, $this->algs);
  • 35. Scriviamo il secondo test: public function testApiEndpointsAreAccessibleWithAValidJWTAuthorizationHeader($method,$route,$params){ $this->setupMocks(); $router = $this->container->get('router'); $uri = $router->generate($route,$params); $this->client->setServerParameter('HTTP_Authorization', 'Bearer '.$this->createValidJWT($this->container->getParameter('secret'))); $this->client->request($method,$uri,$params); $this->assertEquals(200,$this->client->getResponse()->getStatusCode()); } Testiamo che la chiamata ottenga risposta Praticamente identico al precedente, tranne che per il token (stavolta valido)
  • 36. SimplePreAuthenticatorInterface: public function testAuthenticatorCreatesValidTokenIfRequestIsValidAnUserIsPresent(){ $jwt = $this->createValidJWT($this->container->getParameter ('secret')); $request = new Request(); $request->headers->add(array('Authorization'=> Constants:: JWT_BEARER_PREFIX .$jwt)); $this->container->set('marcoalbarelli.api_user_provider',$this- >getMockedUserProvider()); $service = $this->container->get('marcoalbarelli. api_user_authenticator'); $preauthenticatedToken = $service->createToken($request,'pippo'); $this->assertNotNull($preauthenticatedToken); $this->assertEquals($preauthenticatedToken->getCredentials(), $jwt); } public function createToken(Request $request, $providerKey) { …. //Tutto ok $token = new PreAuthenticatedToken($user, $encodedJWT,$providerKey); return $token; }
  • 37. Il cuore dell’autenticazione try { $jwt = $this->jwtCheckerService->decodeToken($encodedJWT); } catch (Exception $exception){ throw new AuthenticationException($exception->getMessage()); } if( !isset($jwt->$apiKeyName)){ throw new BadCredentialsException('Invalid JWT'); } $user = $this->userProvider->findUserByAPIKey($jwt->$apiKeyName); if($user == null){ throw new UsernameNotFoundException("Invalid User"); } … $token = new PreAuthenticatedToken($user,$encodedJWT,$providerKey); return $token; Qui possiamo aggiungere una miriade di controlli sia sul nostro sistema che su altri (grazie all’interoperabilità offerta da JWT)
  • 39. Prossimi passi Implementazione di tutto il flusso OpenID Connect Implementazione dei token JWT cifrati Implementazione di un AP con symfony (soon on a github near you)
  • 41. Conclusioni Symfony + JWT Autenticazione API Abbiamo visto rapidamente come creare un autenticatore per delle API che non si appoggia ai cookie di sessione Abbiamo visto come sia semplice farlo con approccio TDD, essenziale in ambito di sicurezza Abbiamo iniziato ad usare un nuovo standard che ci renderà più facile integrarci con OpenID Connect