SlideShare una empresa de Scribd logo
1 de 114
Descargar para leer sin conexión
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!

Más contenido relacionado

La actualidad más candente

Workshop spring session 2 - La persistance au sein des applications Java
Workshop spring   session 2 - La persistance au sein des applications JavaWorkshop spring   session 2 - La persistance au sein des applications Java
Workshop spring session 2 - La persistance au sein des applications Java
Antoine Rey
 

La actualidad más candente (20)

Vue.js
Vue.jsVue.js
Vue.js
 
Formation Gratuite Total Tests par les experts Java Ippon
Formation Gratuite Total Tests par les experts Java Ippon Formation Gratuite Total Tests par les experts Java Ippon
Formation Gratuite Total Tests par les experts Java Ippon
 
Javascript Prototype Visualized
Javascript Prototype VisualizedJavascript Prototype Visualized
Javascript Prototype Visualized
 
Owl: The New Odoo UI Framework
Owl: The New Odoo UI FrameworkOwl: The New Odoo UI Framework
Owl: The New Odoo UI Framework
 
Java - Lombok
Java - LombokJava - Lombok
Java - Lombok
 
Vue JS Intro
Vue JS IntroVue JS Intro
Vue JS Intro
 
Introduction à spring boot
Introduction à spring bootIntroduction à spring boot
Introduction à spring boot
 
Mastering the Sling Rewriter
Mastering the Sling RewriterMastering the Sling Rewriter
Mastering the Sling Rewriter
 
Important React Hooks
Important React HooksImportant React Hooks
Important React Hooks
 
Workshop spring session 2 - La persistance au sein des applications Java
Workshop spring   session 2 - La persistance au sein des applications JavaWorkshop spring   session 2 - La persistance au sein des applications Java
Workshop spring session 2 - La persistance au sein des applications Java
 
Spring Framework - Spring Security
Spring Framework - Spring SecuritySpring Framework - Spring Security
Spring Framework - Spring Security
 
Formation JPA Avancé / Hibernate gratuite par Ippon 2014
Formation JPA Avancé / Hibernate gratuite par Ippon 2014Formation JPA Avancé / Hibernate gratuite par Ippon 2014
Formation JPA Avancé / Hibernate gratuite par Ippon 2014
 
Spring boot - an introduction
Spring boot - an introductionSpring boot - an introduction
Spring boot - an introduction
 
Doctrine en dehors des sentiers battus
Doctrine en dehors des sentiers battusDoctrine en dehors des sentiers battus
Doctrine en dehors des sentiers battus
 
Spring Boot
Spring BootSpring Boot
Spring Boot
 
Spring Framework Petclinic sample application
Spring Framework Petclinic sample applicationSpring Framework Petclinic sample application
Spring Framework Petclinic sample application
 
Spring boot introduction
Spring boot introductionSpring boot introduction
Spring boot introduction
 
SwtBot: Unit Testing Made Easy
SwtBot: Unit Testing Made EasySwtBot: Unit Testing Made Easy
SwtBot: Unit Testing Made Easy
 
PHP - Introduction to PHP Cookies and Sessions
PHP - Introduction to PHP Cookies and SessionsPHP - Introduction to PHP Cookies and Sessions
PHP - Introduction to PHP Cookies and Sessions
 
PUC SE Day 2019 - SpringBoot
PUC SE Day 2019 - SpringBootPUC SE Day 2019 - SpringBoot
PUC SE Day 2019 - SpringBoot
 

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

Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
Michael Peacock
 

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

Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
How kris-writes-symfony-apps-london
How kris-writes-symfony-apps-londonHow kris-writes-symfony-apps-london
How kris-writes-symfony-apps-london
 
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe KyivKISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
 
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe KyivKISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
KISS: Keep It Simple Security - Oleg Zinchenko - Symfony Cafe Kyiv
 
Keep It Simple Security (Symfony cafe 28-01-2016)
Keep It Simple Security (Symfony cafe 28-01-2016)Keep It Simple Security (Symfony cafe 28-01-2016)
Keep It Simple Security (Symfony cafe 28-01-2016)
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
 
Data Validation models
Data Validation modelsData Validation models
Data Validation models
 
Mashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsMashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web Apps
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 
Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12Symfony components in the wild, PHPNW12
Symfony components in the wild, PHPNW12
 
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...Building Persona: federated and privacy-sensitive identity for the Web (Open ...
Building Persona: federated and privacy-sensitive identity for the Web (Open ...
 
Symfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technologySymfony2 Building on Alpha / Beta technology
Symfony2 Building on Alpha / Beta technology
 
Mashing up JavaScript
Mashing up JavaScriptMashing up JavaScript
Mashing up JavaScript
 
The Web beyond "usernames & passwords" (OSDC12)
The Web beyond "usernames & passwords" (OSDC12)The Web beyond "usernames & passwords" (OSDC12)
The Web beyond "usernames & passwords" (OSDC12)
 
WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
Passwords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answerPasswords suck, but centralized proprietary services are not the answer
Passwords suck, but centralized proprietary services are not the answer
 
Webauthn Tutorial
Webauthn TutorialWebauthn Tutorial
Webauthn Tutorial
 

Más de Ryan Weaver

Symfony2: Get your project started
Symfony2: Get your project startedSymfony2: Get your project started
Symfony2: Get your project started
Ryan Weaver
 
Doctrine2 In 10 Minutes
Doctrine2 In 10 MinutesDoctrine2 In 10 Minutes
Doctrine2 In 10 Minutes
Ryan Weaver
 

Más de Ryan Weaver (19)

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

Último

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
Earley Information Science
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 

Último (20)

Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdf
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 

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!