SlideShare a Scribd company logo
1 of 102
Download to read offline
Guard Authentication:
Powerful, Beautiful Security
by your friend:
Ryan Weaver
@weaverryan
KnpUniversity.com

github.com/weaverryan
Who is this guy?
> Lead for the Symfony documentation

> KnpLabs US - Symfony Consulting, 

training & general Kumbaya

> Writer for KnpUniversity.com Tutorials
> Husband of the much more 

talented @leannapelham
Introducing…
@weaverryan
sfGuardPlugin!
@weaverryan
What’s the hardest

part of Symfony?
@weaverryan
Authentication
Who are you?
@weaverryan
Authorization
Do you have access to do X?
@weaverryan
VOTERS!
@weaverryan
Authentication

in Symfony sucks
@weaverryan
1) Grab information from
the request
@weaverryan
2) Load a User
@weaverryan
3) Validate if the
credentials are valid
@weaverryan
4) authentication success…
now what?
@weaverryan
5) authentication failure …

dang, now what?!
@weaverryan
6) How do we “ask” the
user to login?
@weaverryan
6 Steps

5 Different Classes
@weaverryan
security:

firewalls:

main:

anonymous: ~

logout: ~



form_login: ~

http_basic: ~

some_invented_system_i_created: ~

Each activates a system of these 5 classes
@weaverryan
On Guard!
@weaverryan
interface GuardAuthenticatorInterface

{

public function getCredentials(Request $request);



public function getUser($credentials, $userProvider);



public function checkCredentials($credentials, UserInterface $user);



public function onAuthenticationFailure(Request $request);



public function onAuthenticationSuccess(Request $request, $token);

public function start(Request $request);


public function supportsRememberMe();

}

@weaverryan
Bad News…
@weaverryan
It’s getting coal for
Christmas!
You still have to do work
(sorry Laravel people)
@weaverryan
But it will be simple
@weaverryan
https://github.com/knpuniversity/guard-presentation
You need a User class
(This has nothing to

do with Guard)
@weaverryan
@weaverryan
use SymfonyComponentSecurityCoreUserUserInterface;



class User implements UserInterface

{



}
class User implements UserInterface

{

private $username;



public function __construct($username)

{

$this->username = $username;

}



public function getUsername()

{

return $this->username;

}



public function getRoles()

{

return ['ROLE_USER'];

}



// …

}
a unique identifier

(not really used anywhere)
@weaverryan
class User implements UserInterface

{

// …


public function getPassword()

{

}

public function getSalt()

{

}

public function eraseCredentials()

{

}

}
These are only used for users that

have an encoded password
The Hardest Example Ever:
Form Login
@weaverryan
A traditional login form
setup
@weaverryan
class SecurityController extends Controller

{

/**

* @Route("/login", name="security_login")

*/

public function loginAction()

{

return $this->render('security/login.html.twig');

}



/**

* @Route("/login_check", name="login_check")

*/

public function loginCheckAction()

{

// will never be executed

}

}

<form action="{{ path('login_check') }}” method="post">

<div>

<label for="username">Username</label>

<input name="_username" />

</div>



<div>

<label for="password">Password:</label>

<input type="password" name="_password" />

</div>



<button type="submit">Login</button>

</form>
Let’s create an
authenticator!
@weaverryan
class FormLoginAuthenticator extends AbstractGuardAuthenticator

{

public function getCredentials(Request $request)

{

}



public function getUser($credentials, UserProviderInterface $userProvider)

{

}



public function checkCredentials($credentials, UserInterface $user)

{

}



public function onAuthenticationFailure(Request $request)

{

}



public function onAuthenticationSuccess(Request $request, TokenInterface $token)

{

}



public function start(Request $request, AuthenticationException $e = null)

{

}



public function supportsRememberMe()

{

}

}
public function getCredentials(Request $request)

{

if ($request->getPathInfo() != '/login_check') {

return;

}



return [

'username' => $request->request->get('_username'),

'password' => $request->request->get('_password'),

];

}
Grab the “login” credentials!
@weaverryan
public function getUser($credentials, UserProviderInterface $userProvider)

{

$username = $credentials['username'];



$user = new User();

$user->setUsername($username);



return $user;

}
Create/Load that User!
@weaverryan
public function checkCredentials($credentials, UserInterface $user)

{

$password = $credentials['password'];

if ($password == 'santa' || $password == 'elves') {

return;

}



return true;

}
Are the credentials correct?
@weaverryan
public function onAuthenticationFailure(Request $request,
AuthenticationException $exception)

{

$url = $this->router->generate('security_login');



return new RedirectResponse($url);

}
Crap! Auth failed! Now what!?
@weaverryan
public function onAuthenticationSuccess(Request $request,
TokenInterface $token, $providerKey)

{

$url = $this->router->generate('homepage');



return new RedirectResponse($url);

}
Amazing. Auth worked. Now what?
@weaverryan
public function start(Request $request)

{

$url = $this->router->generate('security_login');



return new RedirectResponse($url);

}
Anonymous user went to /admin

now what?
@weaverryan
Register as a service
services:

form_login_authenticator:

class: AppBundleSecurityFormLoginAuthenticator

arguments: [‘@router’]

@weaverryan
Activate in your firewall
security:

firewalls:

main:

anonymous: ~

logout: ~

guard:

authenticators:

- form_login_authenticator
@weaverryan
AbstractFormLoginAuthenticator
Free login form authenticator code
@weaverryan
User Providers
(This has nothing to

do with Guard)
@weaverryan
Every App has a User class
@weaverryan
And the Christmas spirit
Each User Class Needs 1
User Provider
@weaverryan
class FestiveUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

// "load" the user - e.g. load from the db

$user = new User();

$user->setUsername($username);



return $user;

}



public function refreshUser(UserInterface $user)

{

return $user;

}



public function supportsClass($class)

{

return $class == 'AppBundleEntityUser';

}

}
services:

festive_user_provider:

class: AppBundleSecurityFestiveUserProvider

@weaverryan
security:

providers:

elves:

id: festive_user_provider



firewalls:

main:

anonymous: ~

logout: ~

# this is optional as there is only 1 provider

provider: elves

guard:

authenticators: [form_login_authenticator]

Boom!
Optional Boom!
class FestiveUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

// "load" the user - e.g. load from the db

$user = new User();

$user->setUsername($username);



return $user;

}



public function refreshUser(UserInterface $user)

{

return $user;

}



public function supportsClass($class)

{

return $class == 'AppBundleEntityUser';

}

}
But why!?
class FestiveUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

// "load" the user - e.g. load from the db

$user = new User();

$user->setUsername($username);



return $user;

}



public function refreshUser(UserInterface $user)

{

return $user;

}



public function supportsClass($class)

{

return $class == 'AppBundleEntityUser';

}

}
refresh from the session
class FestiveUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

// "load" the user - e.g. load from the db

$user = new User();

$user->setUsername($username);



return $user;

}



public function refreshUser(UserInterface $user)

{

return $user;

}



public function supportsClass($class)

{

return $class == 'AppBundleEntityUser';

}

}
switch_user, remember_me
Slightly more wonderful:
Loading a User from the Database
@weaverryan
class FestiveUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

$user = $this->em->getRepository('AppBundle:User')

->findOneBy(['username' => $username]);



if (!$user) {

throw new UsernameNotFoundException();

}



return $user;

}

}
@weaverryan
(of course, the “entity” user
provider does this automatically)
public function getUser($credentials, UserProviderInterface $userProvider)

{

$username = $credentials['username'];

//return $userProvider->loadUserByUsername($username);



return $this->em

->getRepository('AppBundle:User')

->findOneBy(['username' => $username]);

}
FormLoginAuthenticator
you can use this if
you want to
… or don’t!
Easiest Example ever:
Api Token Authentication
@weaverryan
Jolliest
1) Client sends a token on an X-
API-TOKEN header

2) We load a User associated
with that token
class User implements UserInterface

{

/**

* @ORMColumn(type="string")

*/

private $apiToken;



// ...

}
class ApiTokenAuthenticator extends AbstractGuardAuthenticator

{

public function getCredentials(Request $request)

{

}



public function getUser($credentials, UserProviderInterface $userProvider)

{

}



public function checkCredentials($credentials, UserInterface $user)

{

}



public function onAuthenticationFailure(Request $request)

{

}



public function onAuthenticationSuccess(Request $request, TokenInterface $token)

{

}



public function start(Request $request, AuthenticationException $e = null)

{

}



public function supportsRememberMe()

{

}

}
public function getCredentials(Request $request)

{

return $request->headers->get('X-API-TOKEN');

}
@weaverryan
public function getUser($credentials, UserProviderInterface $userProvider)

{

$apiToken = $credentials;



return $this->em

->getRepository('AppBundle:User')

->findOneBy(['apiToken' => $apiToken]);

}
@weaverryan
OR
If you use JWT, get the payload from
the token and load the User from it
@weaverryan
public function checkCredentials($credentials, UserInterface $user)

{

// no credentials to check

return true;

}

@weaverryan
public function onAuthenticationFailure(Request $request,
AuthenticationException $exception)

{

return new JsonResponse([

'message' => $exception->getMessageKey()

], 401);

}
@weaverryan
public function onAuthenticationSuccess(Request $request,
TokenInterface $token, $providerKey)

{

// let the request continue to the controller

return;

}
@weaverryan
Register as a service
services:

api_token_authenticator:

class: AppBundleSecurityApiTokenAuthenticator

arguments:

- '@doctrine.orm.entity_manager'
@weaverryan
Activate in your firewall
security:

# ...

firewalls:

main:

# ...

guard:

authenticators:

- form_login_authenticator

- api_token_authenticator

entry_point: form_login_authenticator

which “start” method should be called
curl http://localhost:8000/secure
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="1;url=/login" />
<title>Redirecting to /login</title>
</head>
<body>
Redirecting to <a href="/login">/login</a>.
</body>
</html>
@weaverryan
curl 

--header "X-API-TOKEN: BAD" 

http://localhost:8000/secure
{"message":"Username could not be found."}
@weaverryan
curl 

--header "X-API-TOKEN: GOOD" 

http://localhost:8000/secure
{"message":"Hello from the secureAction!"}
@weaverryan
Social Login!
@weaverryan
!
AUTHENTICATOR
/facebook/check?code=abc
give me user info!
load a User object
"
User
!
composer require league/oauth2-facebook
@weaverryan
@weaverryan
services:

app.facebook_provider:

class: LeagueOAuth2ClientProviderFacebook

arguments:

-

clientId: %facebook_app_id%

clientSecret: %facebook_app_secret%

graphApiVersion: v2.3

redirectUri: "..."

@=service('router').generate('connect_facebook_check', {}, true)
@weaverryan
public function connectFacebookAction()

{

// redirect to Facebook

$facebookOAuthProvider = $this->get('app.facebook_provider');



$url = $facebookOAuthProvider->getAuthorizationUrl([

// these are actually the default scopes

'scopes' => ['public_profile', 'email'],

]);



return $this->redirect($url);

}



/**

* @Route("/connect/facebook-check", name="connect_facebook_check")

*/

public function connectFacebookActionCheck()

{

// will not be reached!

}
class FacebookAuthenticator extends AbstractGuardAuthenticator

{

public function getCredentials(Request $request)

{

}



public function getUser($credentials, UserProviderInterface $userProvider)

{

}



public function checkCredentials($credentials, UserInterface $user)

{

}



public function onAuthenticationFailure(Request $request)

{

}



public function onAuthenticationSuccess(Request $request, TokenInterface $token)

{

}



public function start(Request $request, AuthenticationException $e = null)

{

}



public function supportsRememberMe()

{

}

}
public function getCredentials(Request $request)

{

if ($request->getPathInfo() != '/connect/facebook-check') {

return;

}



return $request->query->get('code');

}
@weaverryan
public function getUser($credentials, …)

{

$authorizationCode = $credentials;



$facebookProvider = $this->container->get('app.facebook_provider');



$accessToken = $facebookProvider->getAccessToken(

'authorization_code',

['code' => $authorizationCode]

);



/** @var FacebookUser $facebookUser */

$facebookUser = $facebookProvider->getResourceOwner($accessToken);



// ...

}
@weaverryan
Now, have some hot chocolate!
@weaverryan
public function getUser($credentials, …)

{
// ...


/** @var FacebookUser $facebookUser */

$facebookUser = $facebookProvider->getResourceOwner($accessToken);



// ...

$em = $this->container->get('doctrine')->getManager();



// 1) have they logged in with Facebook before? Easy!

$user = $em->getRepository('AppBundle:User')

->findOneBy(array('email' => $facebookUser->getEmail()));


if ($user) {

return $user;

}


// ...

}
@weaverryan
public function getUser($credentials, ...)

{

// ...



// 2) no user? Perhaps you just want to create one

// (or redirect to a registration)

$user = new User();

$user->setUsername($facebookUser->getName());

$user->setEmail($facebookUser->getEmail());

$em->persist($user);

$em->flush();
return $user;

}
@weaverryan
public function checkCredentials($credentials, UserInterface $user)

{

// nothing to do here!

}



public function onAuthenticationFailure(Request $request ...)

{

// redirect to login

}



public function onAuthenticationSuccess(Request $request ...)

{

// redirect to homepage / last page

}
@weaverryan
Extra Treats

(no coal)
@weaverryan
Can I control the
error message?
@weaverryan
@weaverryan
If authentication failed, it is because

an AuthenticationException

(or sub-class) was thrown
(This has nothing to

do with Guard)
public function onAuthenticationFailure(Request $request,
AuthenticationException $exception)

{

return new JsonResponse([

'message' => $exception->getMessageKey()

], 401);

}
@weaverryan
Christmas miracle! The exception is passed

when authentication fails
AuthenticationException has a hardcoded

getMessageKey() “safe” string
Invalid
credentials.
public function getCredentials(Request $request)

{

}



public function getUser($credentials, UserProviderInterface $userProvider)

{

}



public function checkCredentials($credentials, UserInterface $user)

{

}

Throw an AuthenticationException at any

time in these 3 methods
How can I customize the message?
@weaverryan
Create a new sub-class of
AuthenticationException for each message
and override getMessageKey()
CustomUserMessageAuthenticationException
@weaverryan
public function getUser($credentials, ...)

{

$apiToken = $credentials;



$user = $this->em

->getRepository('AppBundle:User')

->findOneBy(['apiToken' => $apiToken]);



if (!$user) {

throw new CustomUserMessageAuthenticationException(

'That API token is not very jolly'

);

}



return $user;

}
@weaverryan
I need to manually
authenticate my user
@weaverryan
public function registerAction(Request $request)

{

$user = new User();

$form = // ...



if ($form->isValid()) {

// save the user



$guardHandler = $this->container

->get('security.authentication.guard_handler');



$guardHandler->authenticateUserAndHandleSuccess(

$user,

$request,

$this->get('form_login_authenticator'),

'main' // the name of your firewall

);

// redirect

}

// ...

}
I want to save a
lastLoggedInAt
field on my user no
matter *how* they login
@weaverryan
Chill… that was already
possible
SecurityEvents::INTERACTIVE_LOGIN
@weaverryan
class LastLoginSubscriber implements EventSubscriberInterface

{

public function onInteractiveLogin(InteractiveLoginEvent $event)

{

/** @var User $user */

$user = $event->getAuthenticationToken()->getUser();

$user->setLastLoginTime(new DateTime());

$this->em->persist($user);

$this->em->flush($user);

}



public static function getSubscribedEvents()

{

return [

SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin'

];

}

}

@weaverryan
All of these features

are available now!
@weaverryan
Thanks 2.8!
KnpUGuardBundle
@weaverryan
For those on 2.7
knpuniversity.com/guard
@weaverryan
Ok, what just happened?
1. User implements UserInterface
@weaverryan
2. UserProvider
@weaverryan
3. Create your authenticator(s)
@weaverryan
Authentication#
@weaverryan
@weaverryan
PHP & Symfony Video Tutorials
KnpUniversity.com
Thank You!

More Related Content

What's hot

How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
 
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017Ryan Weaver
 
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)Matthias Noback
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICKonstantin Kudryashov
 
Dealing with Continuous Data Processing, ConFoo 2012
Dealing with Continuous Data Processing, ConFoo 2012Dealing with Continuous Data Processing, ConFoo 2012
Dealing with Continuous Data Processing, ConFoo 2012Michael Peacock
 
Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Samuel ROZE
 
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
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony TechniquesKris Wallsmith
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonKris Wallsmith
 
Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...
Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...
Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...Ville Mattila
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingSamuel ROZE
 
Real time voice call integration - Confoo 2012
Real time voice call integration - Confoo 2012Real time voice call integration - Confoo 2012
Real time voice call integration - Confoo 2012Michael Peacock
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2Hugo Hamon
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDAleix Vergés
 
Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)Fabien Potencier
 

What's hot (20)

How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
Symfony tips and tricks
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricks
 
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
 
The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)The quest for global design principles (SymfonyLive Berlin 2015)
The quest for global design principles (SymfonyLive Berlin 2015)
 
Decoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DICDecoupling with Design Patterns and Symfony2 DIC
Decoupling with Design Patterns and Symfony2 DIC
 
Symfony2 revealed
Symfony2 revealedSymfony2 revealed
Symfony2 revealed
 
Dealing with Continuous Data Processing, ConFoo 2012
Dealing with Continuous Data Processing, ConFoo 2012Dealing with Continuous Data Processing, ConFoo 2012
Dealing with Continuous Data Processing, ConFoo 2012
 
Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)Symfony Messenger (Symfony Live San Francisco)
Symfony Messenger (Symfony Live San Francisco)
 
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
 
Advanced symfony Techniques
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony Techniques
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-london
 
Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...
Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...
Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...
 
Introduction to CQRS and Event Sourcing
Introduction to CQRS and Event SourcingIntroduction to CQRS and Event Sourcing
Introduction to CQRS and Event Sourcing
 
Real time voice call integration - Confoo 2012
Real time voice call integration - Confoo 2012Real time voice call integration - Confoo 2012
Real time voice call integration - Confoo 2012
 
Symfony 2.0 on PHP 5.3
Symfony 2.0 on PHP 5.3Symfony 2.0 on PHP 5.3
Symfony 2.0 on PHP 5.3
 
Speed up your developments with Symfony2
Speed up your developments with Symfony2Speed up your developments with Symfony2
Speed up your developments with Symfony2
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Symfony 2
Symfony 2Symfony 2
Symfony 2
 
Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)Beyond symfony 1.2 (Symfony Camp 2008)
Beyond symfony 1.2 (Symfony Camp 2008)
 

Viewers also liked

Keeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackKeeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackIgnacio Martín
 
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
 
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Ryan Weaver
 
The Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsRyan Weaver
 
Desarrollo código mantenible en WordPress utilizando Symfony
Desarrollo código mantenible en WordPress utilizando SymfonyDesarrollo código mantenible en WordPress utilizando Symfony
Desarrollo código mantenible en WordPress utilizando SymfonyAsier Marqués
 
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014Matthias Noback
 
Hexagonal architecture message-oriented software design
Hexagonal architecture   message-oriented software designHexagonal architecture   message-oriented software design
Hexagonal architecture message-oriented software designMatthias Noback
 
Get Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsDavey Shafik
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages webJean-Pierre Vincent
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phingRajat Pandit
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
 
WordCamp Cantabria - Código mantenible con WordPress
WordCamp Cantabria  - Código mantenible con WordPressWordCamp Cantabria  - Código mantenible con WordPress
WordCamp Cantabria - Código mantenible con WordPressAsier Marqués
 
Top tips my_sql_performance
Top tips my_sql_performanceTop tips my_sql_performance
Top tips my_sql_performanceafup Paris
 
Why elasticsearch rocks!
Why elasticsearch rocks!Why elasticsearch rocks!
Why elasticsearch rocks!tlrx
 
Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Marcello Duarte
 

Viewers also liked (20)

Keeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackKeeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and Webpack
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
 
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
 
The Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony Components
 
Desarrollo código mantenible en WordPress utilizando Symfony
Desarrollo código mantenible en WordPress utilizando SymfonyDesarrollo código mantenible en WordPress utilizando Symfony
Desarrollo código mantenible en WordPress utilizando Symfony
 
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
 
Hexagonal architecture message-oriented software design
Hexagonal architecture   message-oriented software designHexagonal architecture   message-oriented software design
Hexagonal architecture message-oriented software design
 
Get Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP Streams
 
Techniques d'accélération des pages web
Techniques d'accélération des pages webTechniques d'accélération des pages web
Techniques d'accélération des pages web
 
Diving deep into twig
Diving deep into twigDiving deep into twig
Diving deep into twig
 
Elastic Searching With PHP
Elastic Searching With PHPElastic Searching With PHP
Elastic Searching With PHP
 
PHP5.5 is Here
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
 
Automation using-phing
Automation using-phingAutomation using-phing
Automation using-phing
 
Electrify your code with PHP Generators
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
 
WordCamp Cantabria - Código mantenible con WordPress
WordCamp Cantabria  - Código mantenible con WordPressWordCamp Cantabria  - Código mantenible con WordPress
WordCamp Cantabria - Código mantenible con WordPress
 
Doctrine2 sf2Vigo
Doctrine2 sf2VigoDoctrine2 sf2Vigo
Doctrine2 sf2Vigo
 
Top tips my_sql_performance
Top tips my_sql_performanceTop tips my_sql_performance
Top tips my_sql_performance
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Why elasticsearch rocks!
Why elasticsearch rocks!Why elasticsearch rocks!
Why elasticsearch rocks!
 
Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015Understanding Craftsmanship SwanseaCon2015
Understanding Craftsmanship SwanseaCon2015
 

Similar to Guard Authentication: Powerful, Beautiful Security

Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsMichael Peacock
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...Francois Marier
 
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe KyivKISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe KyivGrossum Software Outsourcing
 
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe KyivKISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe KyivGrossum
 
Keep It Simple Security (Symfony cafe 28-01-2016)
Keep It Simple Security (Symfony cafe 28-01-2016)Keep It Simple Security (Symfony cafe 28-01-2016)
Keep It Simple Security (Symfony cafe 28-01-2016)Oleg Zinchenko
 
Passwords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answerPasswords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answerFrancois Marier
 
Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...
Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...
Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...Francois Marier
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful softwareJorn Oomen
 
From 0 to Spring Security 4.0
From 0 to Spring Security 4.0From 0 to Spring Security 4.0
From 0 to Spring Security 4.0robwinch
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015Fernando Daciuk
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
 
Mashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsMashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsBastian Hofmann
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hackingJeroen van Dijk
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ EtsyNishan Subedi
 
The Web beyond "usernames & passwords" (OSDC12)
The Web beyond "usernames & passwords" (OSDC12)The Web beyond "usernames & passwords" (OSDC12)
The Web beyond "usernames & passwords" (OSDC12)Francois Marier
 
Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Yevhen Kotelnytskyi
 

Similar to Guard Authentication: Powerful, Beautiful Security (20)

Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
 
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe KyivKISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
 
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe KyivKISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
 
Keep It Simple Security (Symfony cafe 28-01-2016)
Keep It Simple Security (Symfony cafe 28-01-2016)Keep It Simple Security (Symfony cafe 28-01-2016)
Keep It Simple Security (Symfony cafe 28-01-2016)
 
Passwords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answerPasswords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answer
 
Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...
Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...
Building Persona: federated and privacy-sensitive identity for the Web (LCA 2...
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
From 0 to Spring Security 4.0
From 0 to Spring Security 4.0From 0 to Spring Security 4.0
From 0 to Spring Security 4.0
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Mashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsMashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web Apps
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
Mashing up JavaScript
Mashing up JavaScriptMashing up JavaScript
Mashing up JavaScript
 
Php if
Php ifPhp if
Php if
 
The Web beyond "usernames & passwords" (OSDC12)
The Web beyond "usernames & passwords" (OSDC12)The Web beyond "usernames & passwords" (OSDC12)
The Web beyond "usernames & passwords" (OSDC12)
 
Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0Как получить чёрный пояс по WordPress? v2.0
Как получить чёрный пояс по WordPress? v2.0
 

More from Ryan Weaver

Webpack Encore Symfony Live 2017 San Francisco
Webpack Encore Symfony Live 2017 San FranciscoWebpack Encore Symfony Live 2017 San Francisco
Webpack Encore Symfony Live 2017 San FranciscoRyan Weaver
 
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat
Grand Rapids PHP Meetup: Behavioral Driven Development with BehatGrand Rapids PHP Meetup: Behavioral Driven Development with Behat
Grand Rapids PHP Meetup: Behavioral Driven Development with BehatRyan Weaver
 
Twig: Friendly Curly Braces Invade Your Templates!
Twig: Friendly Curly Braces Invade Your Templates!Twig: Friendly Curly Braces Invade Your Templates!
Twig: Friendly Curly Braces Invade Your Templates!Ryan Weaver
 
Master the New Core of Drupal 8 Now: with Symfony and Silex
Master the New Core of Drupal 8 Now: with Symfony and SilexMaster the New Core of Drupal 8 Now: with Symfony and Silex
Master the New Core of Drupal 8 Now: with Symfony and SilexRyan Weaver
 
Silex: Microframework y camino fácil de aprender Symfony
Silex: Microframework y camino fácil de aprender SymfonySilex: Microframework y camino fácil de aprender Symfony
Silex: Microframework y camino fácil de aprender SymfonyRyan Weaver
 
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love itDrupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love itRyan Weaver
 
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other ToolsCool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other ToolsRyan Weaver
 
A PHP Christmas Miracle - 3 Frameworks, 1 app
A PHP Christmas Miracle - 3 Frameworks, 1 appA PHP Christmas Miracle - 3 Frameworks, 1 app
A PHP Christmas Miracle - 3 Frameworks, 1 appRyan Weaver
 
Symfony2: Get your project started
Symfony2: Get your project startedSymfony2: Get your project started
Symfony2: Get your project startedRyan Weaver
 
Symony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP FrameworkSymony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP FrameworkRyan Weaver
 
Hands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkHands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkRyan Weaver
 
Being Dangerous with Twig (Symfony Live Paris)
Being Dangerous with Twig (Symfony Live Paris)Being Dangerous with Twig (Symfony Live Paris)
Being Dangerous with Twig (Symfony Live Paris)Ryan Weaver
 
Being Dangerous with Twig
Being Dangerous with TwigBeing Dangerous with Twig
Being Dangerous with TwigRyan Weaver
 
Doctrine2 In 10 Minutes
Doctrine2 In 10 MinutesDoctrine2 In 10 Minutes
Doctrine2 In 10 MinutesRyan Weaver
 
Dependency Injection: Make your enemies fear you
Dependency Injection: Make your enemies fear youDependency Injection: Make your enemies fear you
Dependency Injection: Make your enemies fear youRyan Weaver
 
The Art of Doctrine Migrations
The Art of Doctrine MigrationsThe Art of Doctrine Migrations
The Art of Doctrine MigrationsRyan Weaver
 

More from Ryan Weaver (16)

Webpack Encore Symfony Live 2017 San Francisco
Webpack Encore Symfony Live 2017 San FranciscoWebpack Encore Symfony Live 2017 San Francisco
Webpack Encore Symfony Live 2017 San Francisco
 
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat
Grand Rapids PHP Meetup: Behavioral Driven Development with BehatGrand Rapids PHP Meetup: Behavioral Driven Development with Behat
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat
 
Twig: Friendly Curly Braces Invade Your Templates!
Twig: Friendly Curly Braces Invade Your Templates!Twig: Friendly Curly Braces Invade Your Templates!
Twig: Friendly Curly Braces Invade Your Templates!
 
Master the New Core of Drupal 8 Now: with Symfony and Silex
Master the New Core of Drupal 8 Now: with Symfony and SilexMaster the New Core of Drupal 8 Now: with Symfony and Silex
Master the New Core of Drupal 8 Now: with Symfony and Silex
 
Silex: Microframework y camino fácil de aprender Symfony
Silex: Microframework y camino fácil de aprender SymfonySilex: Microframework y camino fácil de aprender Symfony
Silex: Microframework y camino fácil de aprender Symfony
 
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love itDrupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
 
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other ToolsCool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
 
A PHP Christmas Miracle - 3 Frameworks, 1 app
A PHP Christmas Miracle - 3 Frameworks, 1 appA PHP Christmas Miracle - 3 Frameworks, 1 app
A PHP Christmas Miracle - 3 Frameworks, 1 app
 
Symfony2: Get your project started
Symfony2: Get your project startedSymfony2: Get your project started
Symfony2: Get your project started
 
Symony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP FrameworkSymony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP Framework
 
Hands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkHands-on with the Symfony2 Framework
Hands-on with the Symfony2 Framework
 
Being Dangerous with Twig (Symfony Live Paris)
Being Dangerous with Twig (Symfony Live Paris)Being Dangerous with Twig (Symfony Live Paris)
Being Dangerous with Twig (Symfony Live Paris)
 
Being Dangerous with Twig
Being Dangerous with TwigBeing Dangerous with Twig
Being Dangerous with Twig
 
Doctrine2 In 10 Minutes
Doctrine2 In 10 MinutesDoctrine2 In 10 Minutes
Doctrine2 In 10 Minutes
 
Dependency Injection: Make your enemies fear you
Dependency Injection: Make your enemies fear youDependency Injection: Make your enemies fear you
Dependency Injection: Make your enemies fear you
 
The Art of Doctrine Migrations
The Art of Doctrine MigrationsThe Art of Doctrine Migrations
The Art of Doctrine Migrations
 

Recently uploaded

英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作qr0udbr0
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样umasea
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odishasmiwainfosol
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsAhmed Mohamed
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf31events.com
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
Software Coding for software engineering
Software Coding for software engineeringSoftware Coding for software engineering
Software Coding for software engineeringssuserb3a23b
 
Powering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsPowering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsSafe Software
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfExploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfkalichargn70th171
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringHironori Washizaki
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...Technogeeks
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprisepreethippts
 
Comparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfComparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfDrew Moseley
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
 
Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfFerryKemperman
 

Recently uploaded (20)

英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML Diagrams
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
Software Coding for software engineering
Software Coding for software engineeringSoftware Coding for software engineering
Software Coding for software engineering
 
Powering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsPowering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data Streams
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfExploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
 
Machine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their EngineeringMachine Learning Software Engineering Patterns and Their Engineering
Machine Learning Software Engineering Patterns and Their Engineering
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprise
 
Comparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdfComparing Linux OS Image Update Models - EOSS 2024.pdf
Comparing Linux OS Image Update Models - EOSS 2024.pdf
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
 
Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdf
 

Guard Authentication: Powerful, Beautiful Security

  • 1. Guard Authentication: Powerful, Beautiful Security by your friend: Ryan Weaver @weaverryan
  • 2. KnpUniversity.com github.com/weaverryan Who is this guy? > Lead for the Symfony documentation
 > KnpLabs US - Symfony Consulting, training & general Kumbaya > Writer for KnpUniversity.com Tutorials > Husband of the much more talented @leannapelham
  • 5. What’s the hardest part of Symfony? @weaverryan
  • 7. Authorization Do you have access to do X? @weaverryan
  • 10. 1) Grab information from the request @weaverryan
  • 11. 2) Load a User @weaverryan
  • 12. 3) Validate if the credentials are valid @weaverryan
  • 13. 4) authentication success… now what? @weaverryan
  • 14. 5) authentication failure … dang, now what?! @weaverryan
  • 15. 6) How do we “ask” the user to login? @weaverryan
  • 16. 6 Steps 5 Different Classes @weaverryan
  • 17. security:
 firewalls:
 main:
 anonymous: ~
 logout: ~
 
 form_login: ~
 http_basic: ~
 some_invented_system_i_created: ~
 Each activates a system of these 5 classes @weaverryan
  • 19. interface GuardAuthenticatorInterface
 {
 public function getCredentials(Request $request);
 
 public function getUser($credentials, $userProvider);
 
 public function checkCredentials($credentials, UserInterface $user);
 
 public function onAuthenticationFailure(Request $request);
 
 public function onAuthenticationSuccess(Request $request, $token);
 public function start(Request $request); 
 public function supportsRememberMe();
 }
 @weaverryan
  • 21. It’s getting coal for Christmas!
  • 22. You still have to do work (sorry Laravel people) @weaverryan
  • 23. But it will be simple @weaverryan https://github.com/knpuniversity/guard-presentation
  • 24. You need a User class (This has nothing to do with Guard) @weaverryan
  • 26. class User implements UserInterface
 {
 private $username;
 
 public function __construct($username)
 {
 $this->username = $username;
 }
 
 public function getUsername()
 {
 return $this->username;
 }
 
 public function getRoles()
 {
 return ['ROLE_USER'];
 }
 
 // …
 } a unique identifier (not really used anywhere)
  • 27. @weaverryan class User implements UserInterface
 {
 // … 
 public function getPassword()
 {
 }
 public function getSalt()
 {
 }
 public function eraseCredentials()
 {
 }
 } These are only used for users that have an encoded password
  • 28. The Hardest Example Ever: Form Login @weaverryan
  • 29. A traditional login form setup @weaverryan
  • 30. class SecurityController extends Controller
 {
 /**
 * @Route("/login", name="security_login")
 */
 public function loginAction()
 {
 return $this->render('security/login.html.twig');
 }
 
 /**
 * @Route("/login_check", name="login_check")
 */
 public function loginCheckAction()
 {
 // will never be executed
 }
 }

  • 31. <form action="{{ path('login_check') }}” method="post">
 <div>
 <label for="username">Username</label>
 <input name="_username" />
 </div>
 
 <div>
 <label for="password">Password:</label>
 <input type="password" name="_password" />
 </div>
 
 <button type="submit">Login</button>
 </form>
  • 33. class FormLoginAuthenticator extends AbstractGuardAuthenticator
 {
 public function getCredentials(Request $request)
 {
 }
 
 public function getUser($credentials, UserProviderInterface $userProvider)
 {
 }
 
 public function checkCredentials($credentials, UserInterface $user)
 {
 }
 
 public function onAuthenticationFailure(Request $request)
 {
 }
 
 public function onAuthenticationSuccess(Request $request, TokenInterface $token)
 {
 }
 
 public function start(Request $request, AuthenticationException $e = null)
 {
 }
 
 public function supportsRememberMe()
 {
 }
 }
  • 34. public function getCredentials(Request $request)
 {
 if ($request->getPathInfo() != '/login_check') {
 return;
 }
 
 return [
 'username' => $request->request->get('_username'),
 'password' => $request->request->get('_password'),
 ];
 } Grab the “login” credentials! @weaverryan
  • 35. public function getUser($credentials, UserProviderInterface $userProvider)
 {
 $username = $credentials['username'];
 
 $user = new User();
 $user->setUsername($username);
 
 return $user;
 } Create/Load that User! @weaverryan
  • 36. public function checkCredentials($credentials, UserInterface $user)
 {
 $password = $credentials['password'];
 if ($password == 'santa' || $password == 'elves') {
 return;
 }
 
 return true;
 } Are the credentials correct? @weaverryan
  • 37. public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
 {
 $url = $this->router->generate('security_login');
 
 return new RedirectResponse($url);
 } Crap! Auth failed! Now what!? @weaverryan
  • 38. public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
 {
 $url = $this->router->generate('homepage');
 
 return new RedirectResponse($url);
 } Amazing. Auth worked. Now what? @weaverryan
  • 39. public function start(Request $request)
 {
 $url = $this->router->generate('security_login');
 
 return new RedirectResponse($url);
 } Anonymous user went to /admin now what? @weaverryan
  • 40. Register as a service services:
 form_login_authenticator:
 class: AppBundleSecurityFormLoginAuthenticator
 arguments: [‘@router’]
 @weaverryan
  • 41. Activate in your firewall security:
 firewalls:
 main:
 anonymous: ~
 logout: ~
 guard:
 authenticators:
 - form_login_authenticator @weaverryan
  • 42. AbstractFormLoginAuthenticator Free login form authenticator code @weaverryan
  • 43. User Providers (This has nothing to do with Guard) @weaverryan
  • 44. Every App has a User class @weaverryan And the Christmas spirit
  • 45. Each User Class Needs 1 User Provider @weaverryan
  • 46. class FestiveUserProvider implements UserProviderInterface
 {
 public function loadUserByUsername($username)
 {
 // "load" the user - e.g. load from the db
 $user = new User();
 $user->setUsername($username);
 
 return $user;
 }
 
 public function refreshUser(UserInterface $user)
 {
 return $user;
 }
 
 public function supportsClass($class)
 {
 return $class == 'AppBundleEntityUser';
 }
 }
  • 48. security:
 providers:
 elves:
 id: festive_user_provider
 
 firewalls:
 main:
 anonymous: ~
 logout: ~
 # this is optional as there is only 1 provider
 provider: elves
 guard:
 authenticators: [form_login_authenticator]
 Boom! Optional Boom!
  • 49. class FestiveUserProvider implements UserProviderInterface
 {
 public function loadUserByUsername($username)
 {
 // "load" the user - e.g. load from the db
 $user = new User();
 $user->setUsername($username);
 
 return $user;
 }
 
 public function refreshUser(UserInterface $user)
 {
 return $user;
 }
 
 public function supportsClass($class)
 {
 return $class == 'AppBundleEntityUser';
 }
 } But why!?
  • 50. class FestiveUserProvider implements UserProviderInterface
 {
 public function loadUserByUsername($username)
 {
 // "load" the user - e.g. load from the db
 $user = new User();
 $user->setUsername($username);
 
 return $user;
 }
 
 public function refreshUser(UserInterface $user)
 {
 return $user;
 }
 
 public function supportsClass($class)
 {
 return $class == 'AppBundleEntityUser';
 }
 } refresh from the session
  • 51. class FestiveUserProvider implements UserProviderInterface
 {
 public function loadUserByUsername($username)
 {
 // "load" the user - e.g. load from the db
 $user = new User();
 $user->setUsername($username);
 
 return $user;
 }
 
 public function refreshUser(UserInterface $user)
 {
 return $user;
 }
 
 public function supportsClass($class)
 {
 return $class == 'AppBundleEntityUser';
 }
 } switch_user, remember_me
  • 52. Slightly more wonderful: Loading a User from the Database @weaverryan
  • 53. class FestiveUserProvider implements UserProviderInterface
 {
 public function loadUserByUsername($username)
 {
 $user = $this->em->getRepository('AppBundle:User')
 ->findOneBy(['username' => $username]);
 
 if (!$user) {
 throw new UsernameNotFoundException();
 }
 
 return $user;
 }
 } @weaverryan (of course, the “entity” user provider does this automatically)
  • 54. public function getUser($credentials, UserProviderInterface $userProvider)
 {
 $username = $credentials['username'];
 //return $userProvider->loadUserByUsername($username);
 
 return $this->em
 ->getRepository('AppBundle:User')
 ->findOneBy(['username' => $username]);
 } FormLoginAuthenticator you can use this if you want to … or don’t!
  • 55. Easiest Example ever: Api Token Authentication @weaverryan Jolliest
  • 56. 1) Client sends a token on an X- API-TOKEN header 2) We load a User associated with that token class User implements UserInterface
 {
 /**
 * @ORMColumn(type="string")
 */
 private $apiToken;
 
 // ...
 }
  • 57. class ApiTokenAuthenticator extends AbstractGuardAuthenticator
 {
 public function getCredentials(Request $request)
 {
 }
 
 public function getUser($credentials, UserProviderInterface $userProvider)
 {
 }
 
 public function checkCredentials($credentials, UserInterface $user)
 {
 }
 
 public function onAuthenticationFailure(Request $request)
 {
 }
 
 public function onAuthenticationSuccess(Request $request, TokenInterface $token)
 {
 }
 
 public function start(Request $request, AuthenticationException $e = null)
 {
 }
 
 public function supportsRememberMe()
 {
 }
 }
  • 58. public function getCredentials(Request $request)
 {
 return $request->headers->get('X-API-TOKEN');
 } @weaverryan
  • 59. public function getUser($credentials, UserProviderInterface $userProvider)
 {
 $apiToken = $credentials;
 
 return $this->em
 ->getRepository('AppBundle:User')
 ->findOneBy(['apiToken' => $apiToken]);
 } @weaverryan
  • 60. OR If you use JWT, get the payload from the token and load the User from it @weaverryan
  • 61. public function checkCredentials($credentials, UserInterface $user)
 {
 // no credentials to check
 return true;
 }
 @weaverryan
  • 62. public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
 {
 return new JsonResponse([
 'message' => $exception->getMessageKey()
 ], 401);
 } @weaverryan
  • 63. public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
 {
 // let the request continue to the controller
 return;
 } @weaverryan
  • 64. Register as a service services:
 api_token_authenticator:
 class: AppBundleSecurityApiTokenAuthenticator
 arguments:
 - '@doctrine.orm.entity_manager' @weaverryan
  • 65. Activate in your firewall security:
 # ...
 firewalls:
 main:
 # ...
 guard:
 authenticators:
 - form_login_authenticator
 - api_token_authenticator
 entry_point: form_login_authenticator
 which “start” method should be called
  • 66. curl http://localhost:8000/secure <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="refresh" content="1;url=/login" /> <title>Redirecting to /login</title> </head> <body> Redirecting to <a href="/login">/login</a>. </body> </html> @weaverryan
  • 67. curl --header "X-API-TOKEN: BAD" http://localhost:8000/secure {"message":"Username could not be found."} @weaverryan
  • 68. curl --header "X-API-TOKEN: GOOD" http://localhost:8000/secure {"message":"Hello from the secureAction!"} @weaverryan
  • 70. ! AUTHENTICATOR /facebook/check?code=abc give me user info! load a User object " User !
  • 72. @weaverryan services:
 app.facebook_provider:
 class: LeagueOAuth2ClientProviderFacebook
 arguments:
 -
 clientId: %facebook_app_id%
 clientSecret: %facebook_app_secret%
 graphApiVersion: v2.3
 redirectUri: "..."
 @=service('router').generate('connect_facebook_check', {}, true)
  • 73. @weaverryan public function connectFacebookAction()
 {
 // redirect to Facebook
 $facebookOAuthProvider = $this->get('app.facebook_provider');
 
 $url = $facebookOAuthProvider->getAuthorizationUrl([
 // these are actually the default scopes
 'scopes' => ['public_profile', 'email'],
 ]);
 
 return $this->redirect($url);
 }
 
 /**
 * @Route("/connect/facebook-check", name="connect_facebook_check")
 */
 public function connectFacebookActionCheck()
 {
 // will not be reached!
 }
  • 74. class FacebookAuthenticator extends AbstractGuardAuthenticator
 {
 public function getCredentials(Request $request)
 {
 }
 
 public function getUser($credentials, UserProviderInterface $userProvider)
 {
 }
 
 public function checkCredentials($credentials, UserInterface $user)
 {
 }
 
 public function onAuthenticationFailure(Request $request)
 {
 }
 
 public function onAuthenticationSuccess(Request $request, TokenInterface $token)
 {
 }
 
 public function start(Request $request, AuthenticationException $e = null)
 {
 }
 
 public function supportsRememberMe()
 {
 }
 }
  • 75. public function getCredentials(Request $request)
 {
 if ($request->getPathInfo() != '/connect/facebook-check') {
 return;
 }
 
 return $request->query->get('code');
 } @weaverryan
  • 76. public function getUser($credentials, …)
 {
 $authorizationCode = $credentials;
 
 $facebookProvider = $this->container->get('app.facebook_provider');
 
 $accessToken = $facebookProvider->getAccessToken(
 'authorization_code',
 ['code' => $authorizationCode]
 );
 
 /** @var FacebookUser $facebookUser */
 $facebookUser = $facebookProvider->getResourceOwner($accessToken);
 
 // ...
 } @weaverryan
  • 77. Now, have some hot chocolate! @weaverryan
  • 78. public function getUser($credentials, …)
 { // ... 
 /** @var FacebookUser $facebookUser */
 $facebookUser = $facebookProvider->getResourceOwner($accessToken);
 
 // ...
 $em = $this->container->get('doctrine')->getManager();
 
 // 1) have they logged in with Facebook before? Easy!
 $user = $em->getRepository('AppBundle:User')
 ->findOneBy(array('email' => $facebookUser->getEmail())); 
 if ($user) {
 return $user;
 } 
 // ...
 } @weaverryan
  • 79. public function getUser($credentials, ...)
 {
 // ...
 
 // 2) no user? Perhaps you just want to create one
 // (or redirect to a registration)
 $user = new User();
 $user->setUsername($facebookUser->getName());
 $user->setEmail($facebookUser->getEmail());
 $em->persist($user);
 $em->flush(); return $user;
 } @weaverryan
  • 80. public function checkCredentials($credentials, UserInterface $user)
 {
 // nothing to do here!
 }
 
 public function onAuthenticationFailure(Request $request ...)
 {
 // redirect to login
 }
 
 public function onAuthenticationSuccess(Request $request ...)
 {
 // redirect to homepage / last page
 } @weaverryan
  • 82. Can I control the error message? @weaverryan
  • 83. @weaverryan If authentication failed, it is because an AuthenticationException (or sub-class) was thrown (This has nothing to do with Guard)
  • 84. public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
 {
 return new JsonResponse([
 'message' => $exception->getMessageKey()
 ], 401);
 } @weaverryan Christmas miracle! The exception is passed when authentication fails AuthenticationException has a hardcoded getMessageKey() “safe” string Invalid credentials.
  • 85. public function getCredentials(Request $request)
 {
 }
 
 public function getUser($credentials, UserProviderInterface $userProvider)
 {
 }
 
 public function checkCredentials($credentials, UserInterface $user)
 {
 }
 Throw an AuthenticationException at any time in these 3 methods
  • 86. How can I customize the message? @weaverryan Create a new sub-class of AuthenticationException for each message and override getMessageKey()
  • 88. public function getUser($credentials, ...)
 {
 $apiToken = $credentials;
 
 $user = $this->em
 ->getRepository('AppBundle:User')
 ->findOneBy(['apiToken' => $apiToken]);
 
 if (!$user) {
 throw new CustomUserMessageAuthenticationException(
 'That API token is not very jolly'
 );
 }
 
 return $user;
 } @weaverryan
  • 89. I need to manually authenticate my user @weaverryan
  • 90. public function registerAction(Request $request)
 {
 $user = new User();
 $form = // ...
 
 if ($form->isValid()) {
 // save the user
 
 $guardHandler = $this->container
 ->get('security.authentication.guard_handler');
 
 $guardHandler->authenticateUserAndHandleSuccess(
 $user,
 $request,
 $this->get('form_login_authenticator'),
 'main' // the name of your firewall
 );
 // redirect
 }
 // ...
 }
  • 91. I want to save a lastLoggedInAt field on my user no matter *how* they login @weaverryan
  • 92. Chill… that was already possible SecurityEvents::INTERACTIVE_LOGIN @weaverryan
  • 93. class LastLoginSubscriber implements EventSubscriberInterface
 {
 public function onInteractiveLogin(InteractiveLoginEvent $event)
 {
 /** @var User $user */
 $user = $event->getAuthenticationToken()->getUser();
 $user->setLastLoginTime(new DateTime());
 $this->em->persist($user);
 $this->em->flush($user);
 }
 
 public static function getSubscribedEvents()
 {
 return [
 SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin'
 ];
 }
 }
 @weaverryan
  • 94. All of these features are available now! @weaverryan Thanks 2.8!
  • 98. 1. User implements UserInterface @weaverryan
  • 100. 3. Create your authenticator(s) @weaverryan
  • 102. @weaverryan PHP & Symfony Video Tutorials KnpUniversity.com Thank You!