Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.
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 Con...
KnpUniversity.com
KnpUniversity.com
KnpUniversity.com

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

> KnpLabs US - Symfony Con...
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: ~

...
Authentication

in Symfony sucks?
@weaverryan
On Guard!
@weaverryan
interface GuardAuthenticatorInterface

{

public function getCredentials(Request $request);



public function getUser($cr...
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 =...
@weaverryan
class User implements UserInterface

{

// …


public function getPassword()

{

}

public function getSalt()
...
The Hardest Example Ever:
Form Login
@weaverryan
☼ ☼
A traditional login form
setup
@weaverryan
class SecurityController extends Controller

{

/**

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

*/

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

<div>

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

<input name="_user...
Let’s create an
authenticator!
@weaverryan
class FormLoginAuthenticator extends AbstractGuardAuthenticator

{

public function getCredentials(Request $request)

{

}...
public function getCredentials(Request $request)

{

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

return;

}



retur...
public function getUser($credentials, UserProviderInterface $userProvider)

{

$username = $credentials['username'];



$u...
public function checkCredentials($credentials, UserInterface $user)

{

$password = $credentials['password'];

if ($passwo...
public function onAuthenticationFailure(Request $request,
AuthenticationException $exception)

{

$url = $this->router->ge...
public function onAuthenticationSuccess(Request $request,
TokenInterface $token, $providerKey)

{

$url = $this->router->g...
public function start(Request $request)

{

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



return new RedirectRespon...
Register as a service
services:

form_login_authenticator:

class: AppBundleSecurityFormLoginAuthenticator

autowire: true...
Activate in your firewall
security:

firewalls:

main:

anonymous: ~

logout: ~

guard:

authenticators:

- form_login_auth...
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" t...
services:

sunny_user_provider:

class: AppBundleSecuritySunnyUserProvider

@weaverryan
security:

providers:

sunnny_users:

id: sunny_user_provider



firewalls:

main:

anonymous: ~

logout: ~

# this is opt...
class SunnyUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

// "load" t...
class SunnyUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

// "load" t...
class SunnyUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

// "load" t...
Slightly more sunshiney:
Loading a User from the Database
@weaverryan
public function getUser($credentials, UserProviderInterface $userProvider)

{

$username = $credentials['username'];

//re...
class SunnyUserProvider implements UserProviderInterface

{

public function loadUserByUsername($username)

{

$user = $th...
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: B...
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
usernam...
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' => 'RS...
5) Send the token back!
API client app
{

"token": "big_long_json_webtoken"

}
POST /token
username=weaverryan
password=I<...
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');
$h...
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 -pubou...
@weaverryan
3) Point the library at them
# app/config/config.yml

lexik_jwt_authentication:

private_key_path: %kernel.roo...
4) Endpoint to return tokens
/**

* @Route("/token")

*/

public function fetchToken(Request $request)

{

$username = $re...
5) Create the JWT Authenticator
class JwtAuthenticator extends AbstractGuardAuthenticator

{
private $em;

private $jwtEncoder;



public function __const...
public function getCredentials(Request $request)

{

$extractor = new AuthorizationHeaderTokenExtractor(

'Bearer',

'Auth...
public function getUser($credentials, UserProviderInterface $userProvider)

{

$data = $this->jwtEncoder->decode($credenti...
public function checkCredentials($credentials, UserInterface $user)

{

// no credentials to check

return true;

}

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

{

return new JsonResponse(...
public function onAuthenticationSuccess(Request $request,
TokenInterface $token, $providerKey)

{

// let the request cont...
Register as a service
# app/config/services.yml

services:

jwt_authenticator:

class: AppBundleSecurityJwtAuthenticator

...
Activate in your firewall
security:

# ...

firewalls:

main:

# ...

guard:

authenticators:

- form_login_authenticator

...
curl http://localhost:8000/secure
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" conten...
curl 

--header “Authorization: Bearer BAD" 

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

--header “Authorization: Bearer GOOD" 

http://localhost:8000/secure
{"message":"Hello from the secureAction!"}
@we...
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"

face...
@weaverryan
/**

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

*/

public function connectFacebookAction()

{

r...
class FacebookAuthenticator extends AbstractGuardAuthenticator

{

public function getCredentials(Request $request)

{

}
...
public function getCredentials(Request $request)

{

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

return;
...
public function getUser($credentials, …)

{

/** @var AccessToken $accessToken */

$accessToken = $credentials;



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

{

// ...



/** @var FacebookUser $facebookUser */

$facebookUser...
public function getUser($credentials, ...)

{

// ...



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

// (or redire...
public function checkCredentials($credentials, UserInterface $user)

{

// nothing to do here!

}



public function onAut...
* 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 nothi...
public function onAuthenticationFailure(Request $request,
AuthenticationException $exception)

{

return new JsonResponse(...
public function getCredentials(Request $request)

{

}



public function getUser($credentials, UserProviderInterface $use...
How can I customize the message?
@weaverryan
Create a new sub-class of
AuthenticationException for each message
and overri...
CustomUserMessageAuthenticationException
@weaverryan
public function getUser($credentials, ...)

{

$apiToken = $credentials;



$user = $this->em

->getRepository('AppBundle:...
I need to manually
authenticate my user
@weaverryan
public function registerAction(Request $request)

{

$user = new User();

$form = // ...



if ($form->isValid()) {

// sa...
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...
@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/gua...
@weaverryan
New (free) Symfony 3 Tutorial
KnpUniversity.com
Thank You!
Prochain SlideShare
Chargement dans…5
×

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

There are so many interesting ways to authenticate a user: via an API token, social login, a traditional HTML form or anything else you can dream up.

But until now, creating a custom authentication system in Symfony has meant a lot of files and a lot of complexity.

Introducing Guard: a simple, but expandable authentication system built on top of Symfony's security component. Want to authenticate via an API token? Great - that's just one class. Social login? Easy! Have some crazy legacy central authentication system? In this talk, we'll show you how you'd implement any of these in your application today.

Don't get me wrong - you'll still need to do some work. But finally, the path will be clear and joyful.

  • Soyez le premier à commenter

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

  1. 1. Guard Authentication: Powerful, Beautiful Security by your friend: Ryan Weaver @weaverryan
  2. 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
  3. 3. KnpUniversity.com
  4. 4. KnpUniversity.com
  5. 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. 6. What’s the hardest part of Symfony? @weaverryan
  7. 7. Authentication* Who are you? @weaverryan *I am hard
  8. 8. Authorization Do you have access to do X? @weaverryan
  9. 9. VOTERS! @weaverryan
  10. 10. Authentication in Symfony sucks? @weaverryan
  11. 11. 1) Grab information from the request @weaverryan
  12. 12. 2) Load a User @weaverryan
  13. 13. 3) Validate if the credentials are valid @weaverryan
  14. 14. 4) authentication success… now what? @weaverryan
  15. 15. 5) authentication failure … dang, now what?! @weaverryan
  16. 16. 6) How do we “ask” the user to login? @weaverryan
  17. 17. 6 Steps 5 Different Classes @weaverryan
  18. 18. security:
 firewalls:
 main:
 anonymous: ~
 logout: ~
 
 form_login: ~
 http_basic: ~
 some_invented_system_i_created: ~
 Each activates a system of these 5 classes @weaverryan
  19. 19. Authentication in Symfony sucks? @weaverryan
  20. 20. On Guard! @weaverryan
  21. 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
  22. 22. Bad News… @weaverryan
  23. 23. You still have to do work! @weaverryan and wear sunscreen!
  24. 24. But it will be simple @weaverryan https://github.com/knpuniversity/guard-presentation
  25. 25. You need a User class (This has nothing to do with Guard) @weaverryan ☼
  26. 26. @weaverryan use SymfonyComponentSecurityCoreUserUserInterface;
 
 class User implements UserInterface
 {
 
 }
  27. 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. 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. 29. The Hardest Example Ever: Form Login @weaverryan ☼ ☼
  30. 30. A traditional login form setup @weaverryan
  31. 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. 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>
  33. 33. Let’s create an authenticator! @weaverryan
  34. 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. 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. 36. public function getUser($credentials, UserProviderInterface $userProvider)
 {
 $username = $credentials['username'];
 
 $user = new User();
 $user->setUsername($username);
 
 return $user;
 } Create/Load that User! @weaverryan
  37. 37. public function checkCredentials($credentials, UserInterface $user)
 {
 $password = $credentials['password'];
 if ($password == 'santa' || $password == 'elves') {
 return;
 }
 
 return true;
 } Are the credentials correct? @weaverryan
  38. 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. 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. 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. 41. Register as a service services:
 form_login_authenticator:
 class: AppBundleSecurityFormLoginAuthenticator
 autowire: true
 @weaverryan
  42. 42. Activate in your firewall security:
 firewalls:
 main:
 anonymous: ~
 logout: ~
 guard:
 authenticators:
 - form_login_authenticator @weaverryan
  43. 43. User Providers (This has nothing to do with Guard) @weaverryan ☼☼ ☼
  44. 44. Each App has a User class @weaverryan And the Christmas spirit
  45. 45. Each User Class Needs 1 User Provider @weaverryan
  46. 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';
 }
 }
  47. 47. services:
 sunny_user_provider:
 class: AppBundleSecuritySunnyUserProvider
 @weaverryan
  48. 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. 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. 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. 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. 52. Slightly more sunshiney: Loading a User from the Database @weaverryan
  53. 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. 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. 55. Easiest Example ever: Api (JWT) Authentication @weaverryan Warmest
  56. 56. JSON Web Tokens @weaverryan Q) What if an API client could simply send you its user id as authentication? Authorization: Bearer 123
  57. 57. 1) API client authenticates API client Hey dude, I’m weaverryan POST /token username=weaverryan password=I<3php app
  58. 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. 59. 3) Create a package of data API client app $data = [
 'username' => 'weaverryan'
 ];
  60. 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. 61. 5) Send the token back! API client app {
 "token": "big_long_json_webtoken"
 } POST /token username=weaverryan password=I<3php
  62. 62. 6) Client sends the token API client app GET /secret/stuff Authorization: Bearer big_login_json_webtoken
  63. 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. 64. 8) Decode the token 
 $payload = $jws->getPayload();
 
 $username = $payload['username'];

  65. 65. How in Symfony? @weaverryan
  66. 66. @weaverryan 1) Install a library to help sign tokens composer require lexik/jwt-authentication-bundle
  67. 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. 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. 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. 70. 5) Create the JWT Authenticator
  71. 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. 72. public function getCredentials(Request $request)
 {
 $extractor = new AuthorizationHeaderTokenExtractor(
 'Bearer',
 'Authorization'
 );
 
 $token = $extractor->extract($request);
 
 if (false === $token) {
 return;
 }
 
 return $token;
 } @weaverryan
  73. 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. 74. public function checkCredentials($credentials, UserInterface $user)
 {
 // no credentials to check
 return true;
 }
 @weaverryan
  75. 75. public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
 {
 return new JsonResponse([
 'message' => $exception->getMessageKey()
 ], 401);
 } @weaverryan
  76. 76. public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
 {
 // let the request continue to the controller
 return;
 } @weaverryan
  77. 77. Register as a service # app/config/services.yml
 services:
 jwt_authenticator:
 class: AppBundleSecurityJwtAuthenticator
 autowire: true @weaverryan
  78. 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. 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. 80. curl --header “Authorization: Bearer BAD" http://localhost:8000/secure {"message":"Username could not be found."} @weaverryan
  81. 81. curl --header “Authorization: Bearer GOOD" http://localhost:8000/secure {"message":"Hello from the secureAction!"} @weaverryan
  82. 82. Social Login! @weaverryan ☼
  83. 83. AUTHENTICATOR /facebook/check?code=abc give me user info! load a User object User
  84. 84. composer require league/oauth2-facebook knpuniversity/oauth2-client-bundle @weaverryan
  85. 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. 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. 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. 88. public function getCredentials(Request $request)
 {
 if ($request->getPathInfo() != '/connect/facebook-check') {
 return;
 }
 
 return $this->oAuth2Client->getAccessToken($request);
 } @weaverryan
  89. 89. public function getUser($credentials, …)
 {
 /** @var AccessToken $accessToken */
 $accessToken = $credentials;
 
 /** @var FacebookUser $facebookUser */
 $facebookUser = $this->oAuth2Client
 ->fetchUserFromToken($accessToken);
 
 // ...
 } @weaverryan
  90. 90. Now, relax in the shade! @weaverryan
  91. 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. 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. 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. 94. * also supports “finishing registration” @weaverryan
  95. 95. Extra Sunshine (no sunburn) @weaverryan
  96. 96. Can I control the error message? @weaverryan
  97. 97. @weaverryan If authentication failed, it is because an AuthenticationException (or sub-class) was thrown (This has nothing to do with Guard)
  98. 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. 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. 100. How can I customize the message? @weaverryan Create a new sub-class of AuthenticationException for each message and override getMessageKey()
  101. 101. CustomUserMessageAuthenticationException @weaverryan
  102. 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. 103. I need to manually authenticate my user @weaverryan
  104. 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. 105. I want to save a lastLoggedInAt field on my user no matter *how* they login @weaverryan
  106. 106. Chill… that was already possible SecurityEvents::INTERACTIVE_LOGIN @weaverryan
  107. 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. 108. @weaverryan Ok, so how do I make my weird auth system?
  109. 109. 1. User implements UserInterface @weaverryan
  110. 110. 2. UserProvider @weaverryan
  111. 111. 3. Create your authenticator(s) @weaverryan
  112. 112. Authentication @weaverryan
  113. 113. @weaverryan Do it: http://symfony.com/doc/current/cookbook/security/guard-authentication.html http://KnpUniversity.com/guard
  114. 114. @weaverryan New (free) Symfony 3 Tutorial KnpUniversity.com Thank You!

×