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
KnpUniversity.com
KnpUniversity.com
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
What’s the hardest

part of Symfony?
@weaverryan
Authentication*
Who are you?
@weaverryan
*I am hard
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
Authentication

in Symfony sucks?
@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
You still have to do work!
@weaverryan
and wear sunscreen!
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

autowire: true

@weaverryan
Activate in your firewall
security:

firewalls:

main:

anonymous: ~

logout: ~

guard:

authenticators:

- form_login_authenticator
@weaverryan
User Providers
(This has nothing to

do with Guard)
@weaverryan
☼☼ ☼
Each App has a User class
@weaverryan
And the Christmas spirit
Each User Class Needs 1
User Provider
@weaverryan
class SunnyUserProvider 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:

sunny_user_provider:

class: AppBundleSecuritySunnyUserProvider

@weaverryan
security:

providers:

sunnny_users:

id: sunny_user_provider



firewalls:

main:

anonymous: ~

logout: ~

# this is optional as there is only 1 provider

provider: sunny_users

guard:

authenticators: [form_login_authenticator]

Boom!
Optional Boom!
class SunnyUserProvider 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 SunnyUserProvider 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 SunnyUserProvider 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 sunshiney:
Loading a User from the Database
@weaverryan
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!
class SunnyUserProvider 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)
Easiest Example ever:
Api (JWT) Authentication
@weaverryan
Warmest
JSON Web Tokens
@weaverryan
Q) What if an API client could simply
send you its user id as authentication?
Authorization: Bearer 123
1) API client authenticates
API client
Hey dude, I’m weaverryan
POST /token
username=weaverryan
password=I<3php
app
2) Is this really weaverryan?
API client
“It checks out, weaverryan’s
password really is I<3php. Nerd”
POST /token
username=weaverryan
password=I<3php
app
3) Create a package of data
API client app
$data = [

'username' => 'weaverryan'

];
4) Sign the data!
$data = [

'username' => 'weaverryan'

];



// package: namshi/jose

$jws = new SimpleJWS(['alg' => 'RS256']);

$jws->setPayload($data);



$privateKey = openssl_pkey_get_private(

'file://path/to/private.key'

);

$jws->sign($privateKey);



$token = $jws->getTokenString()

5) Send the token back!
API client app
{

"token": "big_long_json_webtoken"

}
POST /token
username=weaverryan
password=I<3php
6) Client sends the token
API client app
GET /secret/stuff
Authorization: Bearer big_login_json_webtoken
7) Verify the signature
// "Authorization: Bearer 123" -> "123"

$authHeader = $request->headers->get('Authorization');
$headerParts = explode(' ', $authHeader);

$token = $headerParts[1];



$jws = SimpleJWS::load($token);

$public_key = openssl_pkey_get_public(

'/path/to/public.key'

);

if (!$jws->isValid($public_key, 'RS256')) {

die('go away >:(')

}
8) Decode the token


$payload = $jws->getPayload();



$username = $payload['username'];

How in Symfony?
@weaverryan
@weaverryan
1) Install a library to help sign tokens
composer require lexik/jwt-authentication-bundle
@weaverryan
2) Create a public & private key
mkdir var/jwt
openssl genrsa -out var/jwt/private.pem 4096
openssl rsa -pubout -in var/jwt/private.pem 
-out var/jwt/public.pem
@weaverryan
3) Point the library at them
# app/config/config.yml

lexik_jwt_authentication:

private_key_path: %kernel.root_dir%/../var/jwt/private.pem

public_key_path: %kernel.root_dir%/../var/jwt/public.pem

4) Endpoint to return tokens
/**

* @Route("/token")

*/

public function fetchToken(Request $request)

{

$username = $request->request->get('username');

$password = $request->request->get('password');



$user = $this->getDoctrine()

->getRepository('AppBundle:User')

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

if (!$user) {

throw $this->createNotFoundException();

}



// check password



$token = $this->get('lexik_jwt_authentication.encoder')

->encode(['username' => $user->getUsername()]);



return new JsonResponse(['token' => $token]);

}
5) Create the JWT Authenticator
class JwtAuthenticator extends AbstractGuardAuthenticator

{
private $em;

private $jwtEncoder;



public function __construct(EntityManager $em, JWTEncoder $jwtEncoder)

{

$this->em = $em;

$this->jwtEncoder = $jwtEncoder;

}


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 getCredentials(Request $request)

{

$extractor = new AuthorizationHeaderTokenExtractor(

'Bearer',

'Authorization'

);



$token = $extractor->extract($request);



if (false === $token) {

return;

}



return $token;

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

{

$data = $this->jwtEncoder->decode($credentials);



if (!$data) {

return;

}



$username = $data['username'];



return $this->em

->getRepository('AppBundle:User')

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

}
@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
# app/config/services.yml

services:

jwt_authenticator:

class: AppBundleSecurityJwtAuthenticator

autowire: true
@weaverryan
Activate in your firewall
security:

# ...

firewalls:

main:

# ...

guard:

authenticators:

- form_login_authenticator

- jwt_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 “Authorization: Bearer BAD" 

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

--header “Authorization: Bearer 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 
knpuniversity/oauth2-client-bundle
@weaverryan
@weaverryan
# app/config/config.yml

knpu_oauth2_client:

clients:

# creates service: "knpu.oauth2.client.facebook"

facebook:

type: facebook

client_id: %facebook_client_id%

client_secret: %facebook_client_secret%

redirect_route: connect_facebook_check

graph_api_version: v2.5
@weaverryan
/**

* @Route("/connect/facebook", name="connect_facebook")

*/

public function connectFacebookAction()

{

return $this->get('knpu.oauth2.client.facebook')

->redirect(['public_profile', 'email']);

}



/**

* @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 $this->oAuth2Client->getAccessToken($request);

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

{

/** @var AccessToken $accessToken */

$accessToken = $credentials;



/** @var FacebookUser $facebookUser */

$facebookUser = $this->oAuth2Client

->fetchUserFromToken($accessToken);



// ...

}
@weaverryan
Now, relax in the shade!
@weaverryan
@weaverryan
public function getUser($credentials, ...)

{

// ...



/** @var FacebookUser $facebookUser */

$facebookUser = $this->oAuth2Client

->fetchUserFromToken($accessToken);



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

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

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



if ($user) {

return $user;

}



// ...

}
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
* also supports “finishing registration”
@weaverryan
Extra Sunshine

(no sunburn)
@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
Beach Vacation Bonus! 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 stormy'

);

}



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
@weaverryan
Ok, so how do I make
my weird auth system?
1. User implements UserInterface
@weaverryan
2. UserProvider
@weaverryan
3. Create your authenticator(s)
@weaverryan
Authentication
@weaverryan
@weaverryan
Do it:
http://symfony.com/doc/current/cookbook/security/guard-authentication.html
http://KnpUniversity.com/guard
@weaverryan
New (free) Symfony 3 Tutorial
KnpUniversity.com
Thank You!

More Related Content

What's hot (20)

PDF
Pipeline oriented programming
Scott Wlaschin
 
PDF
Kotlin Coroutines Reloaded
Roman Elizarov
 
PDF
L18 Object Relational Mapping
Ólafur Andri Ragnarsson
 
PDF
How I started to love design patterns
Samuel ROZE
 
PPT
Oracle 10g Forms Lesson 7
KAMA3
 
PPTX
Java Foundations: Basic Syntax, Conditions, Loops
Svetlin Nakov
 
PDF
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
Shengyou Fan
 
PDF
cmake.pdf
Thejeswara Reddy
 
PDF
Oaf personaliztion examples
Kaushik Kumar Kuberanathan
 
PPT
Oracle Forms: Menu
Sekhar Byna
 
PDF
Kotlin Bytecode Generation and Runtime Performance
intelliyole
 
DOCX
Fast formula queries for functions, contexts, db is and packages
Feras Ahmad
 
PPT
Oracle Forms : Validation Triggers
Sekhar Byna
 
PDF
DBI database Items Query Oracle Fusion Cloud
Feras Ahmad
 
PDF
Infolets and OTBI Deep link Actionable Reports - Configuration Work Book
Feras Ahmad
 
PDF
2017 Pycon KR - Django/AWS 를 이용한 쇼핑몰 서비스 구축
Youngil Cho
 
PDF
The Functional Programming Toolkit (NDC Oslo 2019)
Scott Wlaschin
 
PPTX
Integration of APEX and Oracle Forms
Roel Hartman
 
PPT
Oracle Forms Tutorial (www.aboutoracleapps.com)
magupta26
 
PPTX
SCIM: Why It’s More Important, and More Simple, Than You Think - CIS 2014
Kelly Grizzle
 
Pipeline oriented programming
Scott Wlaschin
 
Kotlin Coroutines Reloaded
Roman Elizarov
 
L18 Object Relational Mapping
Ólafur Andri Ragnarsson
 
How I started to love design patterns
Samuel ROZE
 
Oracle 10g Forms Lesson 7
KAMA3
 
Java Foundations: Basic Syntax, Conditions, Loops
Svetlin Nakov
 
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
Shengyou Fan
 
cmake.pdf
Thejeswara Reddy
 
Oaf personaliztion examples
Kaushik Kumar Kuberanathan
 
Oracle Forms: Menu
Sekhar Byna
 
Kotlin Bytecode Generation and Runtime Performance
intelliyole
 
Fast formula queries for functions, contexts, db is and packages
Feras Ahmad
 
Oracle Forms : Validation Triggers
Sekhar Byna
 
DBI database Items Query Oracle Fusion Cloud
Feras Ahmad
 
Infolets and OTBI Deep link Actionable Reports - Configuration Work Book
Feras Ahmad
 
2017 Pycon KR - Django/AWS 를 이용한 쇼핑몰 서비스 구축
Youngil Cho
 
The Functional Programming Toolkit (NDC Oslo 2019)
Scott Wlaschin
 
Integration of APEX and Oracle Forms
Roel Hartman
 
Oracle Forms Tutorial (www.aboutoracleapps.com)
magupta26
 
SCIM: Why It’s More Important, and More Simple, Than You Think - CIS 2014
Kelly Grizzle
 

Similar to Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more (20)

PDF
Guard Authentication: Powerful, Beautiful Security
Ryan Weaver
 
PDF
You Shall Not Pass - Security in Symfony
The Software House
 
PDF
Love and Loss: A Symfony Security Play
Kris Wallsmith
 
KEY
Symfony2 security layer
Manuel Baldassarri
 
KEY
IoC with PHP
Chris Weldon
 
PPTX
SymfonyCon 2015 - A symphony of developers
Radu Murzea
 
KEY
Phpne august-2012-symfony-components-friends
Michael Peacock
 
PDF
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
Arc & Codementor
 
PDF
How kris-writes-symfony-apps-london
Kris Wallsmith
 
PDF
Dependency injection in PHP 5.3/5.4
Fabien Potencier
 
PDF
Dependency injection-zendcon-2010
Fabien Potencier
 
PDF
The state of Symfony2 - SymfonyDay 2010
Fabien Potencier
 
PDF
Build powerfull and smart web applications with Symfony2
Hugo Hamon
 
PDF
Dependency Injection with PHP and PHP 5.3
Fabien Potencier
 
PDF
Dependency Injection with PHP 5.3
Fabien Potencier
 
PPTX
Php security common 2011
10n Software, LLC
 
PDF
Dependency Injection IPC 201
Fabien Potencier
 
PDF
Dependency injection - phpday 2010
Fabien Potencier
 
PDF
Dependency Injection
Fabien Potencier
 
PDF
Dependency Injection - ConFoo 2010
Fabien Potencier
 
Guard Authentication: Powerful, Beautiful Security
Ryan Weaver
 
You Shall Not Pass - Security in Symfony
The Software House
 
Love and Loss: A Symfony Security Play
Kris Wallsmith
 
Symfony2 security layer
Manuel Baldassarri
 
IoC with PHP
Chris Weldon
 
SymfonyCon 2015 - A symphony of developers
Radu Murzea
 
Phpne august-2012-symfony-components-friends
Michael Peacock
 
Building Modern and Secure PHP Applications – Codementor Office Hours with Be...
Arc & Codementor
 
How kris-writes-symfony-apps-london
Kris Wallsmith
 
Dependency injection in PHP 5.3/5.4
Fabien Potencier
 
Dependency injection-zendcon-2010
Fabien Potencier
 
The state of Symfony2 - SymfonyDay 2010
Fabien Potencier
 
Build powerfull and smart web applications with Symfony2
Hugo Hamon
 
Dependency Injection with PHP and PHP 5.3
Fabien Potencier
 
Dependency Injection with PHP 5.3
Fabien Potencier
 
Php security common 2011
10n Software, LLC
 
Dependency Injection IPC 201
Fabien Potencier
 
Dependency injection - phpday 2010
Fabien Potencier
 
Dependency Injection
Fabien Potencier
 
Dependency Injection - ConFoo 2010
Fabien Potencier
 
Ad

More from Ryan Weaver (20)

PDF
Webpack Encore Symfony Live 2017 San Francisco
Ryan Weaver
 
PDF
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
Ryan Weaver
 
PDF
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Ryan Weaver
 
PDF
Symfony: Your Next Microframework (SymfonyCon 2015)
Ryan Weaver
 
PDF
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat
Ryan Weaver
 
PDF
Twig: Friendly Curly Braces Invade Your Templates!
Ryan Weaver
 
PDF
Master the New Core of Drupal 8 Now: with Symfony and Silex
Ryan Weaver
 
PDF
Silex: Microframework y camino fácil de aprender Symfony
Ryan Weaver
 
PDF
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
Ryan Weaver
 
PDF
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Ryan Weaver
 
PDF
The Wonderful World of Symfony Components
Ryan Weaver
 
PDF
A PHP Christmas Miracle - 3 Frameworks, 1 app
Ryan Weaver
 
PDF
Symfony2: Get your project started
Ryan Weaver
 
PDF
Symony2 A Next Generation PHP Framework
Ryan Weaver
 
PDF
Hands-on with the Symfony2 Framework
Ryan Weaver
 
PDF
Being Dangerous with Twig (Symfony Live Paris)
Ryan Weaver
 
PDF
Being Dangerous with Twig
Ryan Weaver
 
PDF
Doctrine2 In 10 Minutes
Ryan Weaver
 
PDF
Dependency Injection: Make your enemies fear you
Ryan Weaver
 
PDF
The Art of Doctrine Migrations
Ryan Weaver
 
Webpack Encore Symfony Live 2017 San Francisco
Ryan Weaver
 
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
Ryan Weaver
 
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Ryan Weaver
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Ryan Weaver
 
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat
Ryan Weaver
 
Twig: Friendly Curly Braces Invade Your Templates!
Ryan Weaver
 
Master the New Core of Drupal 8 Now: with Symfony and Silex
Ryan Weaver
 
Silex: Microframework y camino fácil de aprender Symfony
Ryan Weaver
 
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
Ryan Weaver
 
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Ryan Weaver
 
The Wonderful World of Symfony Components
Ryan Weaver
 
A PHP Christmas Miracle - 3 Frameworks, 1 app
Ryan Weaver
 
Symfony2: Get your project started
Ryan Weaver
 
Symony2 A Next Generation PHP Framework
Ryan Weaver
 
Hands-on with the Symfony2 Framework
Ryan Weaver
 
Being Dangerous with Twig (Symfony Live Paris)
Ryan Weaver
 
Being Dangerous with Twig
Ryan Weaver
 
Doctrine2 In 10 Minutes
Ryan Weaver
 
Dependency Injection: Make your enemies fear you
Ryan Weaver
 
The Art of Doctrine Migrations
Ryan Weaver
 
Ad

Recently uploaded (20)

PDF
Peak of Data & AI Encore AI-Enhanced Workflows for the Real World
Safe Software
 
PPTX
Agentforce World Tour Toronto '25 - MCP with MuleSoft
Alexandra N. Martinez
 
PDF
Bitcoin for Millennials podcast with Bram, Power Laws of Bitcoin
Stephen Perrenod
 
PDF
NLJUG Speaker academy 2025 - first session
Bert Jan Schrijver
 
PDF
“Computer Vision at Sea: Automated Fish Tracking for Sustainable Fishing,” a ...
Edge AI and Vision Alliance
 
PDF
AI Agents in the Cloud: The Rise of Agentic Cloud Architecture
Lilly Gracia
 
PDF
Transforming Utility Networks: Large-scale Data Migrations with FME
Safe Software
 
PPTX
COMPARISON OF RASTER ANALYSIS TOOLS OF QGIS AND ARCGIS
Sharanya Sarkar
 
PPTX
Agentforce World Tour Toronto '25 - Supercharge MuleSoft Development with Mod...
Alexandra N. Martinez
 
PPTX
MuleSoft MCP Support (Model Context Protocol) and Use Case Demo
shyamraj55
 
PDF
What’s my job again? Slides from Mark Simos talk at 2025 Tampa BSides
Mark Simos
 
PDF
UPDF - AI PDF Editor & Converter Key Features
DealFuel
 
PPTX
The Project Compass - GDG on Campus MSIT
dscmsitkol
 
PPTX
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
DOCX
Cryptography Quiz: test your knowledge of this important security concept.
Rajni Bhardwaj Grover
 
PDF
[Newgen] NewgenONE Marvin Brochure 1.pdf
darshakparmar
 
DOCX
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
PDF
The Rise of AI and IoT in Mobile App Tech.pdf
IMG Global Infotech
 
PDF
“Voice Interfaces on a Budget: Building Real-time Speech Recognition on Low-c...
Edge AI and Vision Alliance
 
PDF
Go Concurrency Real-World Patterns, Pitfalls, and Playground Battles.pdf
Emily Achieng
 
Peak of Data & AI Encore AI-Enhanced Workflows for the Real World
Safe Software
 
Agentforce World Tour Toronto '25 - MCP with MuleSoft
Alexandra N. Martinez
 
Bitcoin for Millennials podcast with Bram, Power Laws of Bitcoin
Stephen Perrenod
 
NLJUG Speaker academy 2025 - first session
Bert Jan Schrijver
 
“Computer Vision at Sea: Automated Fish Tracking for Sustainable Fishing,” a ...
Edge AI and Vision Alliance
 
AI Agents in the Cloud: The Rise of Agentic Cloud Architecture
Lilly Gracia
 
Transforming Utility Networks: Large-scale Data Migrations with FME
Safe Software
 
COMPARISON OF RASTER ANALYSIS TOOLS OF QGIS AND ARCGIS
Sharanya Sarkar
 
Agentforce World Tour Toronto '25 - Supercharge MuleSoft Development with Mod...
Alexandra N. Martinez
 
MuleSoft MCP Support (Model Context Protocol) and Use Case Demo
shyamraj55
 
What’s my job again? Slides from Mark Simos talk at 2025 Tampa BSides
Mark Simos
 
UPDF - AI PDF Editor & Converter Key Features
DealFuel
 
The Project Compass - GDG on Campus MSIT
dscmsitkol
 
From Sci-Fi to Reality: Exploring AI Evolution
Svetlana Meissner
 
Cryptography Quiz: test your knowledge of this important security concept.
Rajni Bhardwaj Grover
 
[Newgen] NewgenONE Marvin Brochure 1.pdf
darshakparmar
 
Python coding for beginners !! Start now!#
Rajni Bhardwaj Grover
 
The Rise of AI and IoT in Mobile App Tech.pdf
IMG Global Infotech
 
“Voice Interfaces on a Budget: Building Real-time Speech Recognition on Low-c...
Edge AI and Vision Alliance
 
Go Concurrency Real-World Patterns, Pitfalls, and Playground Battles.pdf
Emily Achieng
 

Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more

  • 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. 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
  • 6. What’s the hardest part of Symfony? @weaverryan
  • 8. Authorization Do you have access to do X? @weaverryan
  • 11. 1) Grab information from the request @weaverryan
  • 12. 2) Load a User @weaverryan
  • 13. 3) Validate if the credentials are valid @weaverryan
  • 14. 4) authentication success… now what? @weaverryan
  • 15. 5) authentication failure … dang, now what?! @weaverryan
  • 16. 6) How do we “ask” the user to login? @weaverryan
  • 17. 6 Steps 5 Different Classes @weaverryan
  • 18. security:
 firewalls:
 main:
 anonymous: ~
 logout: ~
 
 form_login: ~
 http_basic: ~
 some_invented_system_i_created: ~
 Each activates a system of these 5 classes @weaverryan
  • 21. 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
  • 23. You still have to do work! @weaverryan and wear sunscreen!
  • 24. But it will be simple @weaverryan https://github.com/knpuniversity/guard-presentation
  • 25. You need a User class (This has nothing to do with Guard) @weaverryan ☼
  • 27. 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)
  • 28. @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
  • 29. The Hardest Example Ever: Form Login @weaverryan ☼ ☼
  • 30. A traditional login form setup @weaverryan
  • 31. 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
 }
 }

  • 32. <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>
  • 34. 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()
 {
 }
 }
  • 35. 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
  • 36. public function getUser($credentials, UserProviderInterface $userProvider)
 {
 $username = $credentials['username'];
 
 $user = new User();
 $user->setUsername($username);
 
 return $user;
 } Create/Load that User! @weaverryan
  • 37. public function checkCredentials($credentials, UserInterface $user)
 {
 $password = $credentials['password'];
 if ($password == 'santa' || $password == 'elves') {
 return;
 }
 
 return true;
 } Are the credentials correct? @weaverryan
  • 38. public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
 {
 $url = $this->router->generate('security_login');
 
 return new RedirectResponse($url);
 } Crap! Auth failed! Now what!? @weaverryan
  • 39. public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
 {
 $url = $this->router->generate('homepage');
 
 return new RedirectResponse($url);
 } Amazing. Auth worked. Now what? @weaverryan
  • 40. public function start(Request $request)
 {
 $url = $this->router->generate('security_login');
 
 return new RedirectResponse($url);
 } Anonymous user went to /admin now what? @weaverryan
  • 41. Register as a service services:
 form_login_authenticator:
 class: AppBundleSecurityFormLoginAuthenticator
 autowire: true
 @weaverryan
  • 42. Activate in your firewall security:
 firewalls:
 main:
 anonymous: ~
 logout: ~
 guard:
 authenticators:
 - form_login_authenticator @weaverryan
  • 43. User Providers (This has nothing to do with Guard) @weaverryan ☼☼ ☼
  • 44. Each App has a User class @weaverryan And the Christmas spirit
  • 45. Each User Class Needs 1 User Provider @weaverryan
  • 46. class SunnyUserProvider 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:
 sunnny_users:
 id: sunny_user_provider
 
 firewalls:
 main:
 anonymous: ~
 logout: ~
 # this is optional as there is only 1 provider
 provider: sunny_users
 guard:
 authenticators: [form_login_authenticator]
 Boom! Optional Boom!
  • 49. class SunnyUserProvider 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 SunnyUserProvider 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 SunnyUserProvider 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 sunshiney: Loading a User from the Database @weaverryan
  • 53. 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!
  • 54. class SunnyUserProvider 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)
  • 55. Easiest Example ever: Api (JWT) Authentication @weaverryan Warmest
  • 56. JSON Web Tokens @weaverryan Q) What if an API client could simply send you its user id as authentication? Authorization: Bearer 123
  • 57. 1) API client authenticates API client Hey dude, I’m weaverryan POST /token username=weaverryan password=I<3php app
  • 58. 2) Is this really weaverryan? API client “It checks out, weaverryan’s password really is I<3php. Nerd” POST /token username=weaverryan password=I<3php app
  • 59. 3) Create a package of data API client app $data = [
 'username' => 'weaverryan'
 ];
  • 60. 4) Sign the data! $data = [
 'username' => 'weaverryan'
 ];
 
 // package: namshi/jose
 $jws = new SimpleJWS(['alg' => 'RS256']);
 $jws->setPayload($data);
 
 $privateKey = openssl_pkey_get_private(
 'file://path/to/private.key'
 );
 $jws->sign($privateKey);
 
 $token = $jws->getTokenString()

  • 61. 5) Send the token back! API client app {
 "token": "big_long_json_webtoken"
 } POST /token username=weaverryan password=I<3php
  • 62. 6) Client sends the token API client app GET /secret/stuff Authorization: Bearer big_login_json_webtoken
  • 63. 7) Verify the signature // "Authorization: Bearer 123" -> "123"
 $authHeader = $request->headers->get('Authorization'); $headerParts = explode(' ', $authHeader);
 $token = $headerParts[1];
 
 $jws = SimpleJWS::load($token);
 $public_key = openssl_pkey_get_public(
 '/path/to/public.key'
 );
 if (!$jws->isValid($public_key, 'RS256')) {
 die('go away >:(')
 }
  • 64. 8) Decode the token 
 $payload = $jws->getPayload();
 
 $username = $payload['username'];

  • 66. @weaverryan 1) Install a library to help sign tokens composer require lexik/jwt-authentication-bundle
  • 67. @weaverryan 2) Create a public & private key mkdir var/jwt openssl genrsa -out var/jwt/private.pem 4096 openssl rsa -pubout -in var/jwt/private.pem -out var/jwt/public.pem
  • 68. @weaverryan 3) Point the library at them # app/config/config.yml
 lexik_jwt_authentication:
 private_key_path: %kernel.root_dir%/../var/jwt/private.pem
 public_key_path: %kernel.root_dir%/../var/jwt/public.pem

  • 69. 4) Endpoint to return tokens /**
 * @Route("/token")
 */
 public function fetchToken(Request $request)
 {
 $username = $request->request->get('username');
 $password = $request->request->get('password');
 
 $user = $this->getDoctrine()
 ->getRepository('AppBundle:User')
 ->findOneBy(['username' => $username]);
 if (!$user) {
 throw $this->createNotFoundException();
 }
 
 // check password
 
 $token = $this->get('lexik_jwt_authentication.encoder')
 ->encode(['username' => $user->getUsername()]);
 
 return new JsonResponse(['token' => $token]);
 }
  • 70. 5) Create the JWT Authenticator
  • 71. class JwtAuthenticator extends AbstractGuardAuthenticator
 { private $em;
 private $jwtEncoder;
 
 public function __construct(EntityManager $em, JWTEncoder $jwtEncoder)
 {
 $this->em = $em;
 $this->jwtEncoder = $jwtEncoder;
 } 
 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)
 {
 }
 
 // …
 }
  • 72. public function getCredentials(Request $request)
 {
 $extractor = new AuthorizationHeaderTokenExtractor(
 'Bearer',
 'Authorization'
 );
 
 $token = $extractor->extract($request);
 
 if (false === $token) {
 return;
 }
 
 return $token;
 } @weaverryan
  • 73. public function getUser($credentials, UserProviderInterface $userProvider)
 {
 $data = $this->jwtEncoder->decode($credentials);
 
 if (!$data) {
 return;
 }
 
 $username = $data['username'];
 
 return $this->em
 ->getRepository('AppBundle:User')
 ->findOneBy(['username' => $username]);
 } @weaverryan
  • 74. public function checkCredentials($credentials, UserInterface $user)
 {
 // no credentials to check
 return true;
 }
 @weaverryan
  • 75. public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
 {
 return new JsonResponse([
 'message' => $exception->getMessageKey()
 ], 401);
 } @weaverryan
  • 76. public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
 {
 // let the request continue to the controller
 return;
 } @weaverryan
  • 77. Register as a service # app/config/services.yml
 services:
 jwt_authenticator:
 class: AppBundleSecurityJwtAuthenticator
 autowire: true @weaverryan
  • 78. Activate in your firewall security:
 # ...
 firewalls:
 main:
 # ...
 guard:
 authenticators:
 - form_login_authenticator
 - jwt_authenticator
 entry_point: form_login_authenticator
 which “start” method should be called
  • 79. 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
  • 80. curl --header “Authorization: Bearer BAD" http://localhost:8000/secure {"message":"Username could not be found."} @weaverryan
  • 81. curl --header “Authorization: Bearer GOOD" http://localhost:8000/secure {"message":"Hello from the secureAction!"} @weaverryan
  • 84. composer require league/oauth2-facebook knpuniversity/oauth2-client-bundle @weaverryan
  • 85. @weaverryan # app/config/config.yml
 knpu_oauth2_client:
 clients:
 # creates service: "knpu.oauth2.client.facebook"
 facebook:
 type: facebook
 client_id: %facebook_client_id%
 client_secret: %facebook_client_secret%
 redirect_route: connect_facebook_check
 graph_api_version: v2.5
  • 86. @weaverryan /**
 * @Route("/connect/facebook", name="connect_facebook")
 */
 public function connectFacebookAction()
 {
 return $this->get('knpu.oauth2.client.facebook')
 ->redirect(['public_profile', 'email']);
 }
 
 /**
 * @Route("/connect/facebook-check", name="connect_facebook_check")
 */
 public function connectFacebookActionCheck()
 {
 // will not be reached!
 }
  • 87. 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()
 {
 }
 }
  • 88. public function getCredentials(Request $request)
 {
 if ($request->getPathInfo() != '/connect/facebook-check') {
 return;
 }
 
 return $this->oAuth2Client->getAccessToken($request);
 } @weaverryan
  • 89. public function getUser($credentials, …)
 {
 /** @var AccessToken $accessToken */
 $accessToken = $credentials;
 
 /** @var FacebookUser $facebookUser */
 $facebookUser = $this->oAuth2Client
 ->fetchUserFromToken($accessToken);
 
 // ...
 } @weaverryan
  • 90. Now, relax in the shade! @weaverryan
  • 91. @weaverryan public function getUser($credentials, ...)
 {
 // ...
 
 /** @var FacebookUser $facebookUser */
 $facebookUser = $this->oAuth2Client
 ->fetchUserFromToken($accessToken);
 
 // 1) have they logged in with Facebook before? Easy!
 $user = $this->em->getRepository('AppBundle:User')
 ->findOneBy(array('email' => $facebookUser->getEmail()));
 
 if ($user) {
 return $user;
 }
 
 // ...
 }
  • 92. 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
  • 93. 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
  • 94. * also supports “finishing registration” @weaverryan
  • 96. Can I control the error message? @weaverryan
  • 97. @weaverryan If authentication failed, it is because an AuthenticationException (or sub-class) was thrown (This has nothing to do with Guard)
  • 98. public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
 {
 return new JsonResponse([
 'message' => $exception->getMessageKey()
 ], 401);
 } @weaverryan Beach Vacation Bonus! The exception is passed when authentication fails AuthenticationException has a hardcoded getMessageKey() “safe” string Invalid credentials.
  • 99. 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
  • 100. How can I customize the message? @weaverryan Create a new sub-class of AuthenticationException for each message and override getMessageKey()
  • 102. public function getUser($credentials, ...)
 {
 $apiToken = $credentials;
 
 $user = $this->em
 ->getRepository('AppBundle:User')
 ->findOneBy(['apiToken' => $apiToken]);
 
 if (!$user) {
 throw new CustomUserMessageAuthenticationException(
 'That API token is stormy'
 );
 }
 
 return $user;
 } @weaverryan
  • 103. I need to manually authenticate my user @weaverryan
  • 104. 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
 }
 // ...
 }
  • 105. I want to save a lastLoggedInAt field on my user no matter *how* they login @weaverryan
  • 106. Chill… that was already possible SecurityEvents::INTERACTIVE_LOGIN @weaverryan
  • 107. 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
  • 108. @weaverryan Ok, so how do I make my weird auth system?
  • 109. 1. User implements UserInterface @weaverryan
  • 111. 3. Create your authenticator(s) @weaverryan
  • 114. @weaverryan New (free) Symfony 3 Tutorial KnpUniversity.com Thank You!