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!

Guard Authentication: Powerful, Beautiful Security

  • 1.
    Guard Authentication: Powerful, BeautifulSecurity by your friend: Ryan Weaver @weaverryan
  • 2.
    KnpUniversity.com github.com/weaverryan Who is thisguy? > Lead for the Symfony documentation
 > KnpLabs US - Symfony Consulting, training & general Kumbaya > Writer for KnpUniversity.com Tutorials > Husband of the much more talented @leannapelham
  • 3.
  • 4.
  • 5.
    What’s the hardest partof Symfony? @weaverryan
  • 6.
  • 7.
    Authorization Do you haveaccess to do X? @weaverryan
  • 8.
  • 9.
  • 10.
    1) Grab informationfrom the request @weaverryan
  • 11.
    2) Load aUser @weaverryan
  • 12.
    3) Validate ifthe credentials are valid @weaverryan
  • 13.
  • 14.
    5) authentication failure… dang, now what?! @weaverryan
  • 15.
    6) How dowe “ask” the user to login? @weaverryan
  • 16.
    6 Steps 5 DifferentClasses @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
  • 18.
  • 19.
    interface GuardAuthenticatorInterface
 {
 public functiongetCredentials(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
  • 20.
  • 21.
    It’s getting coalfor Christmas!
  • 22.
    You still haveto do work (sorry Laravel people) @weaverryan
  • 23.
    But it willbe simple @weaverryan https://github.com/knpuniversity/guard-presentation
  • 24.
    You need aUser class (This has nothing to do with Guard) @weaverryan
  • 25.
  • 26.
    class User implementsUserInterface
 {
 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 implementsUserInterface
 {
 // … 
 public function getPassword()
 {
 }
 public function getSalt()
 {
 }
 public function eraseCredentials()
 {
 }
 } These are only used for users that have an encoded password
  • 28.
    The Hardest ExampleEver: Form Login @weaverryan
  • 29.
    A traditional loginform setup @weaverryan
  • 30.
    class SecurityController extendsController
 {
 /**
 * @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>
  • 32.
  • 33.
    class FormLoginAuthenticator extendsAbstractGuardAuthenticator
 {
 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 aservice services:
 form_login_authenticator:
 class: AppBundleSecurityFormLoginAuthenticator
 arguments: [‘@router’]
 @weaverryan
  • 41.
    Activate in yourfirewall security:
 firewalls:
 main:
 anonymous: ~
 logout: ~
 guard:
 authenticators:
 - form_login_authenticator @weaverryan
  • 42.
    AbstractFormLoginAuthenticator Free login formauthenticator code @weaverryan
  • 43.
    User Providers (This hasnothing to do with Guard) @weaverryan
  • 44.
    Every App hasa User class @weaverryan And the Christmas spirit
  • 45.
    Each User ClassNeeds 1 User Provider @weaverryan
  • 46.
    class FestiveUserProvider implementsUserProviderInterface
 {
 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';
 }
 }
  • 47.
  • 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 implementsUserProviderInterface
 {
 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 implementsUserProviderInterface
 {
 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 implementsUserProviderInterface
 {
 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: Loadinga User from the Database @weaverryan
  • 53.
    class FestiveUserProvider implementsUserProviderInterface
 {
 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: ApiToken Authentication @weaverryan Jolliest
  • 56.
    1) Client sendsa 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 extendsAbstractGuardAuthenticator
 {
 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 useJWT, 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 aservice services:
 api_token_authenticator:
 class: AppBundleSecurityApiTokenAuthenticator
 arguments:
 - '@doctrine.orm.entity_manager' @weaverryan
  • 65.
    Activate in yourfirewall 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> <metacharset="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
  • 69.
  • 70.
  • 71.
  • 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 extendsAbstractGuardAuthenticator
 {
 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 somehot 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
  • 81.
  • 82.
    Can I controlthe 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 Icustomize the message? @weaverryan Create a new sub-class of AuthenticationException for each message and override getMessageKey()
  • 87.
  • 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 tomanually 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 tosave a lastLoggedInAt field on my user no matter *how* they login @weaverryan
  • 92.
    Chill… that wasalready possible SecurityEvents::INTERACTIVE_LOGIN @weaverryan
  • 93.
    class LastLoginSubscriber implementsEventSubscriberInterface
 {
 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 thesefeatures are available now! @weaverryan Thanks 2.8!
  • 95.
  • 96.
  • 97.
  • 98.
    1. User implementsUserInterface @weaverryan
  • 99.
  • 100.
    3. Create yourauthenticator(s) @weaverryan
  • 101.
  • 102.
    @weaverryan PHP & SymfonyVideo Tutorials KnpUniversity.com Thank You!