SlideShare a Scribd company logo
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

PDF
Love and Loss: A Symfony Security Play
PDF
Symfony & Javascript. Combining the best of two worlds
PDF
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
PDF
Design how your objects talk through mocking
PDF
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
PDF
Matters of State
PDF
How Kris Writes Symfony Apps
PDF
How I started to love design patterns
Love and Loss: A Symfony Security Play
Symfony & Javascript. Combining the best of two worlds
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Design how your objects talk through mocking
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
Matters of State
How Kris Writes Symfony Apps
How I started to love design patterns

What's hot (20)

PDF
How Kris Writes Symfony Apps
PDF
Symfony tips and tricks
PDF
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
PDF
The quest for global design principles (SymfonyLive Berlin 2015)
PDF
Decoupling with Design Patterns and Symfony2 DIC
PDF
Symfony2 revealed
PPTX
Dealing with Continuous Data Processing, ConFoo 2012
PDF
Symfony Messenger (Symfony Live San Francisco)
ODP
Rich domain model with symfony 2.5 and doctrine 2.5
PDF
Advanced symfony Techniques
ODP
Symfony2, creare bundle e valore per il cliente
PDF
How kris-writes-symfony-apps-london
PDF
Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...
PDF
Introduction to CQRS and Event Sourcing
PPTX
Real time voice call integration - Confoo 2012
PDF
Symfony 2.0 on PHP 5.3
PPTX
Speed up your developments with Symfony2
PDF
Decoupling the Ulabox.com monolith. From CRUD to DDD
PDF
Symfony 2
PDF
Beyond symfony 1.2 (Symfony Camp 2008)
How Kris Writes Symfony Apps
Symfony tips and tricks
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The quest for global design principles (SymfonyLive Berlin 2015)
Decoupling with Design Patterns and Symfony2 DIC
Symfony2 revealed
Dealing with Continuous Data Processing, ConFoo 2012
Symfony Messenger (Symfony Live San Francisco)
Rich domain model with symfony 2.5 and doctrine 2.5
Advanced symfony Techniques
Symfony2, creare bundle e valore per il cliente
How kris-writes-symfony-apps-london
Running a Scalable And Reliable Symfony2 Application in Cloud (Symfony Sweden...
Introduction to CQRS and Event Sourcing
Real time voice call integration - Confoo 2012
Symfony 2.0 on PHP 5.3
Speed up your developments with Symfony2
Decoupling the Ulabox.com monolith. From CRUD to DDD
Symfony 2
Beyond symfony 1.2 (Symfony Camp 2008)
Ad

Viewers also liked (20)

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

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

PDF
You Shall Not Pass - Security in Symfony
KEY
IoC with PHP
KEY
Phpne august-2012-symfony-components-friends
PPTX
SymfonyCon 2015 - A symphony of developers
PDF
Using API Platform to build ticketing system #symfonycon
PDF
Build powerfull and smart web applications with Symfony2
PDF
Dependency injection-zendcon-2010
PDF
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
PDF
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
PDF
Data Validation models
PDF
Dependency injection in PHP 5.3/5.4
PDF
Alexander Makarov "Let’s talk about code"
KEY
Symfony2 security layer
PDF
Dependency Injection IPC 201
PDF
Dependency injection - phpday 2010
PDF
Dependency Injection
PDF
Dependency Injection - ConFoo 2010
PDF
Dependency Injection with PHP and PHP 5.3
PDF
Dependency Injection with PHP 5.3
PDF
The state of Symfony2 - SymfonyDay 2010
You Shall Not Pass - Security in Symfony
IoC with PHP
Phpne august-2012-symfony-components-friends
SymfonyCon 2015 - A symphony of developers
Using API Platform to build ticketing system #symfonycon
Build powerfull and smart web applications with Symfony2
Dependency injection-zendcon-2010
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
Data Validation models
Dependency injection in PHP 5.3/5.4
Alexander Makarov "Let’s talk about code"
Symfony2 security layer
Dependency Injection IPC 201
Dependency injection - phpday 2010
Dependency Injection
Dependency Injection - ConFoo 2010
Dependency Injection with PHP and PHP 5.3
Dependency Injection with PHP 5.3
The state of Symfony2 - SymfonyDay 2010

More from Ryan Weaver (16)

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

Recently uploaded (20)

PPTX
Online Work Permit System for Fast Permit Processing
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
Understanding Forklifts - TECH EHS Solution
PDF
Softaken Excel to vCard Converter Software.pdf
PDF
Digital Strategies for Manufacturing Companies
PPTX
L1 - Introduction to python Backend.pptx
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
PPTX
Introduction to Artificial Intelligence
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
top salesforce developer skills in 2025.pdf
PDF
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
PDF
Nekopoi APK 2025 free lastest update
PPTX
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
PDF
How Creative Agencies Leverage Project Management Software.pdf
Online Work Permit System for Fast Permit Processing
Adobe Illustrator 28.6 Crack My Vision of Vector Design
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
Design an Analysis of Algorithms II-SECS-1021-03
Navsoft: AI-Powered Business Solutions & Custom Software Development
Understanding Forklifts - TECH EHS Solution
Softaken Excel to vCard Converter Software.pdf
Digital Strategies for Manufacturing Companies
L1 - Introduction to python Backend.pptx
Design an Analysis of Algorithms I-SECS-1021-03
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
Introduction to Artificial Intelligence
How to Choose the Right IT Partner for Your Business in Malaysia
How to Migrate SBCGlobal Email to Yahoo Easily
PTS Company Brochure 2025 (1).pdf.......
top salesforce developer skills in 2025.pdf
Flood Susceptibility Mapping Using Image-Based 2D-CNN Deep Learnin. Overview ...
Nekopoi APK 2025 free lastest update
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
How Creative Agencies Leverage Project Management Software.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!