From ac107c7f3f9007eb841673c40ef463476ed364c3 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 16:45:31 -0400 Subject: [PATCH 1/8] WIP documentation for the new guard auth Just a bootstrap to explain things, and start discussion --- cookbook/security/guard-api-key.rst | 485 +++++++++++++++++++++ cookbook/security/guard-authentication.rst | 12 + 2 files changed, 497 insertions(+) create mode 100644 cookbook/security/guard-api-key.rst create mode 100644 cookbook/security/guard-authentication.rst diff --git a/cookbook/security/guard-api-key.rst b/cookbook/security/guard-api-key.rst new file mode 100644 index 00000000000..76f6256fcbd --- /dev/null +++ b/cookbook/security/guard-api-key.rst @@ -0,0 +1,485 @@ +.. index:: + single: Security; Custom Request Authenticator + +How to Authenticate Users with an API Key using Guard +===================================================== + +Nowadays, it's quite usual to authenticate the user via an API key (when developing +a web service for instance). The API key is provided for every request and is +passed as a query string parameter or via an HTTP header. + +Setting up the authenticator is just 3 steps: + +* :ref:`cookbook-guard-api-authenticator` +* :ref:`cookbook-guard-api-configuration` +* :ref:`cookbook-guard-api-user-provider` + +.. _cookbook-guard-api-authenticator: + +A) Create the Guard Authenticator +--------------------------------- + +Suppose you want to read an ``X-API-TOKEN`` header on each request and use +that to authenticate the user. To do this, create a class that implements +:class:`Symfony\\Component\\Security\\Guard\\\GuardAuthenticatorInterface`:: + + // src/AppBundle/Security/TokenAuthenticator.php + namespace AppBundle\Security; + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; + use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; + use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\Exception\AuthenticationException; + use Symfony\Component\Security\Core\User\UserProviderInterface; + + class TokenAuthenticator extends AbstractGuardAuthenticator + { + public function getCredentialsFromRequest(Request $request) + { + $token = $request->headers->get('X-AUTH-TOKEN'); + + // no token? Don't do anything :D + if (!$token) { + return; + } + + return [ + 'token' => $token, + ]; + } + + public function authenticate($credentials, UserProviderInterface $userProvider) + { + $token = $credentials['token']; + + // call a method on your UserProvider - see below for details + $user = $userProvider->loadUserByToken($token); + + if (!$user) { + throw new AuthenticationCredentialsNotFoundException(); + } + + return $user; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + // on success, just let the request keep going! + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + $data = array( + // you might translate this message + 'message' => $exception->getMessageKey() + ); + + return new Response(json_encode($data), 403); + } + + /** + * Called when authentication is needed, but it's not sent + */ + public function start(Request $request, AuthenticationException $authException = null) + { + $data = array( + // you might translate this message + 'message' => 'Authentication Required' + ); + + return new Response(json_encode($data), 401); + } + + public function supportsRememberMe() + { + return false; + } + } + +Nice work! You're not done yet, but let's look at each piece: + +**getCredentialsFromRequest()** + The guard system calls this method on *every* request and your job is + to read the token from the request and return it (it'll be passed to + ``authenticate()``). If you return ``null``, the rest of the authentication + process is skipped. + +**authenticate** + If you returned something from ``getCredentialsFromRequest()``, that + value is passed here as ``$credentials``. This is the *core* of the authentication + process. Your job is to use ``$credentials`` to return a ``UserInterface`` + object *or* throw an :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException` + (e.g. if the token does not exist). *How* you do this is up to you and + you'll learn more in :ref:`user provider ` + section. + +**onAuthenticationSuccess** + This is called after successful authentication and your job is to either + return a :class:`Symfony\\Component\\HttpFoundation\\Response` object + that will be sent to the client or ``null`` to continue the request + (e.g. allow the route/controller to be called like normal). Since this + is an API where each request authenticates itself, you want to return + ``nul``. + +**onAuthenticationFailure** + This is called if authentication fails (i.e. if you throw an + :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`) + inside ``authenticate()`` or ``getCredentialsFromRequest()``. Your job + is to return the :class:`Symfony\\Component\\HttpFoundation\\Response` + object that should be sent to the client. + +**start** + This is called if the client accesses a URI/resource that requires authentication, + but no authentication details were sent (i.e. you returned ``null`` from + ``getCredentialsFromRequest()``). Your job is to return a + :class:`Symfony\\Component\\HttpFoundation\\Response` object that helps + the user authenticate (e.g. a 401 response that says "token is missing!"). + +**supportsRememberMe** + Since this is a stateless API, you do not want to support "remember me" + functionality. + +.. _cookbook-guard-api-configuration: + +B) Configure the Service and security.yml +----------------------------------------- + +To use your configurator, you'll need to register it as a service: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + app.token_authenticator: + class: AppBundle\Security\TokenAuthenticator + arguments: [] + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // app/config/services.php + + // ... + $container + ->register('app.token_authenticator', 'AppBundle\Security\TokenAuthenticator'); + +Now you can configure your firewall to use the ``guard`` authentication system +and your new ``app.token_authenticator`` authenticator: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + # ... + + firewalls: + secured_area: + pattern: ^/ + # set to false if you *do* want to store users in the session + stateless: true + guard: + authenticators: + - app.token_authenticator + + .. code-block:: xml + + + + + + + + + + apikey_authenticator + + + + + + .. code-block:: php + + // app/config/security.php + + // .. + + $container->loadFromExtension('security', array( + 'firewalls' => array( + 'secured_area' => array( + 'pattern' => '^/', + 'stateless' => true, + 'simple_preauth' => array( + 'authenticators' => array( + 'app.token_authenticator' + ), + ), + ), + ), + )); + +Perfect! Now that the security system knows about your authenticator, the +last step is to configure your "user provider" and make it work nicely with +the authenticator. + +C) Adding the User Provider +--------------------------- + +Even though you're authenticating with an API token, the end result is that +a "User" object is set on the security system. Returning this user is the goal +of your configurator's ``authenticate()`` method. + +To help out, you'll need to configure a :ref:`user provider `. +A few core providers exist (including one that :ref:`loads users from Doctrine `), +but creating one that does exactly what you want is easy. + +Suppose you're using Doctrine and have a ``User`` entity that has a ``token`` +property that can be used to authenticate with your API (the ``User`` entity +class isn't shown here). To load users from that entity, create a class that +implements :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`:: + + namespace AppBundle\Security; + + use AppBundle\Repository\UserRepository; + use Doctrine\ORM\EntityManager; + use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; + use Symfony\Component\Security\Core\User\UserInterface; + use Symfony\Component\Security\Core\User\UserProviderInterface; + + class UserProvider implements UserProviderInterface + { + private $em; + + public function __construct(EntityManager $em) + { + $this->em = $em; + } + + // UserProviderInterface + public function loadUserByUsername($username) + { + $user = $this->getUserRepository()->findOneBy(array('username' => $username)); + + if (null === $user) { + throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); + } + + return $user; + } + + // UserProviderInterface + public function refreshUser(UserInterface $user) + { + $user = $this->getUserRepository()->find($user->getId()); + if (!$user) { + throw new UsernameNotFoundException(sprintf('User with id "%s" not found!', $user->getId())); + } + + return $user; + } + + // UserProviderInterface + public function supportsClass($class) + { + return $class === get_class($this) || is_subclass_of($class, get_class($this)); + } + + // our own custom method + public function loadUserByToken($token) + { + return $this->getUserRepository()->findOneBy(array( + 'token' => $token + )); + } + + /** + * @return UserRepository + */ + private function getUserRepository() + { + return $this->em->getRepository('AppBundle:User'); + } + } + +Most of these methods are part of ``UserInterface`` and are used internally. +But ``loadUserByToken()`` is a custom method that you'll use in a moment. + +Register your brand-new "user provider" as a service: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + app.user_provider: + class: AppBundle\Security\UserProvider + arguments: ['@doctrine.orm.entity_manager'] + + + .. code-block:: xml + + + + + + + + + doctrine.orm.entity_manager + + + + + .. code-block:: php + + // app/config/services.php + + // ... + $container + ->register('app.user_provider', 'AppBundle\Security\UserProvider') + ->setArguments(array( + new Reference('doctrine.orm.entity_manager') + )); + +And finally plug this into your security system and tell your firewall that +this is your provider: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + # ... + + providers: + # this key could be anything, but it's referenced below + main_provider: + id: app.user_provider + + firewalls: + secured_area: + # all the existing stuff ... + provider: main_provider + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: php + + // app/config/security.php + + // .. + + $container->loadFromExtension('security', array( + 'firewalls' => array( + 'secured_area' => array( + // ... + 'provider' => 'main_provider' + ), + ), + 'providers' => array( + 'main_provider' => array( + 'id' => 'app.user_provider', + ), + ), + )); + +Great work! Because of this, when ``authenticate()`` is called on your ``TokenAuthenticator``, +the ``$userProvider`` argument will be *your* ``UserProvider`` class. This +means you can add whatever methods to ``UserProvider`` that you want, and +then use them inside ``authenticate()``. + +As a reminder, the ``TokenAuthenticator::authenticate()`` looks like this:: + + // src/AppBundle/Security/TokenAuthenticator.php + // ... + + public function authenticate($credentials, UserProviderInterface $userProvider) + { + $token = $credentials['token']; + + // call a method on your UserProvider - see below for details + $user = $userProvider->loadUserByToken($token); + + if (!$user) { + throw new AuthenticationCredentialsNotFoundException(); + } + + return $user; + } + +To query for a ``User`` object whose ``token`` property matches the ``$token``, +you can use the ``UserProvider::loadUserByToken()`` that was added a moment +ago. It makes that query and reutrns the ``User`` object. + +This is just *one* example: you could add whatever methods to ``UserProvider`` +that you want or use whatever logic you want to return the ``User`` object. + +Doing more with Guard Auth +-------------------------- + +Now that you know how to authenticate with a token, see what else you can +do: + +Doing more with Guard Auth +-------------------------- + +Now that you know how to authenticate with a token, see what else you can +do: + +* :doc:`Creating a Login Form ` +* :doc:`Using Multiple Authenticators (Login form *and* API Token) ` + diff --git a/cookbook/security/guard-authentication.rst b/cookbook/security/guard-authentication.rst new file mode 100644 index 00000000000..c27886c264e --- /dev/null +++ b/cookbook/security/guard-authentication.rst @@ -0,0 +1,12 @@ +.. index:: + single: Security; Custom Authentication + +How to Authenticate in any way using Guard +========================================== + +Whether you're creating a traditional login form or authenticating via an +API token, using the guard authentication system will make this painless. + +* :doc:`Creating a Login Form ` +* :doc:`Authenticating with an API Token ` +* :doc:`Using Multiple Authenticators (Login form *and* API Token) ` From 9e411fee438c4ac23e56a6280a124c3310d80512 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 20:51:00 -0400 Subject: [PATCH 2/8] I'm extending the abstract class - so mention that. Also adding anonymous You probably want anonymous, because without it, if you return null so that the listener does nothing, you'll get an exception from the AccessListener. This article is written more where you don't *force* auth in the authenticator, but then if a resource is protected, it of course requires it. --- cookbook/security/guard-api-key.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cookbook/security/guard-api-key.rst b/cookbook/security/guard-api-key.rst index 76f6256fcbd..25fc83ba11b 100644 --- a/cookbook/security/guard-api-key.rst +++ b/cookbook/security/guard-api-key.rst @@ -20,8 +20,9 @@ A) Create the Guard Authenticator --------------------------------- Suppose you want to read an ``X-API-TOKEN`` header on each request and use -that to authenticate the user. To do this, create a class that implements -:class:`Symfony\\Component\\Security\\Guard\\\GuardAuthenticatorInterface`:: +that to authenticate the user. To do this, create a class that extends +:class:`Symfony\\Component\\Security\\Guard\\AbstractGuardAuthenticator` +(or which implements :class:`Symfony\\Component\\Security\\Guard\\GuardAuthenticatorInterface`):: // src/AppBundle/Security/TokenAuthenticator.php namespace AppBundle\Security; @@ -199,6 +200,7 @@ and your new ``app.token_authenticator`` authenticator: pattern: ^/ # set to false if you *do* want to store users in the session stateless: true + anonymous: true guard: authenticators: - app.token_authenticator @@ -218,6 +220,7 @@ and your new ``app.token_authenticator`` authenticator: apikey_authenticator @@ -237,6 +240,7 @@ and your new ``app.token_authenticator`` authenticator: 'secured_area' => array( 'pattern' => '^/', 'stateless' => true, + 'anonymous' => true, 'simple_preauth' => array( 'authenticators' => array( 'app.token_authenticator' From bfce91b5b1b16ee98d4db29b6486cd3d5ee63261 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 18 May 2015 10:06:17 -0400 Subject: [PATCH 3/8] Fixing minor comments --- cookbook/security/guard-api-key.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cookbook/security/guard-api-key.rst b/cookbook/security/guard-api-key.rst index 25fc83ba11b..abf4967769d 100644 --- a/cookbook/security/guard-api-key.rst +++ b/cookbook/security/guard-api-key.rst @@ -46,9 +46,9 @@ that to authenticate the user. To do this, create a class that extends return; } - return [ + return array( 'token' => $token, - ]; + ); } public function authenticate($credentials, UserProviderInterface $userProvider) @@ -241,7 +241,7 @@ and your new ``app.token_authenticator`` authenticator: 'pattern' => '^/', 'stateless' => true, 'anonymous' => true, - 'simple_preauth' => array( + 'guard' => array( 'authenticators' => array( 'app.token_authenticator' ), From 440fe6f4d04e78dba22876fc363b96b78b766292 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 22 Nov 2015 18:31:06 -0500 Subject: [PATCH 4/8] revamping Guard article --- cookbook/security/api_key_authentication.rst | 5 + .../custom_authentication_provider.rst | 1 + .../custom_password_authenticator.rst | 5 + cookbook/security/guard-api-key.rst | 489 --------------- cookbook/security/guard-authentication.rst | 573 +++++++++++++++++- 5 files changed, 580 insertions(+), 493 deletions(-) delete mode 100644 cookbook/security/guard-api-key.rst diff --git a/cookbook/security/api_key_authentication.rst b/cookbook/security/api_key_authentication.rst index f2e9fc7e824..da7de4f8502 100644 --- a/cookbook/security/api_key_authentication.rst +++ b/cookbook/security/api_key_authentication.rst @@ -4,6 +4,11 @@ How to Authenticate Users with API Keys ======================================= +.. tip:: + + Check out :doc:`/cookbook/security/guard-authentication` for a simpler and more + flexible way to accomplish custom authentication tasks like this. + Nowadays, it's quite usual to authenticate the user via an API key (when developing a web service for instance). The API key is provided for every request and is passed as a query string parameter or via an HTTP header. diff --git a/cookbook/security/custom_authentication_provider.rst b/cookbook/security/custom_authentication_provider.rst index 0d57295277a..f44ea0b8cf8 100644 --- a/cookbook/security/custom_authentication_provider.rst +++ b/cookbook/security/custom_authentication_provider.rst @@ -10,6 +10,7 @@ How to Create a custom Authentication Provider you through that process. But depending on your needs, you may be able to solve your problem in a simpler, or via a community bundle: + * :doc:`/cookbook/security/guard-authentication` * :doc:`/cookbook/security/custom_password_authenticator` * :doc:`/cookbook/security/api_key_authentication` * To authenticate via OAuth using a third-party service such as Google, Facebook diff --git a/cookbook/security/custom_password_authenticator.rst b/cookbook/security/custom_password_authenticator.rst index c8d54869c11..c71995b7402 100644 --- a/cookbook/security/custom_password_authenticator.rst +++ b/cookbook/security/custom_password_authenticator.rst @@ -4,6 +4,11 @@ How to Create a Custom Form Password Authenticator ================================================== +.. tip:: + + Check out :doc:`/cookbook/security/guard-authentication` for a simpler and more + flexible way to accomplish custom authentication tasks like this. + Imagine you want to allow access to your website only between 2pm and 4pm UTC. Before Symfony 2.4, you had to create a custom token, factory, listener and provider. In this entry, you'll learn how to do this for a login form diff --git a/cookbook/security/guard-api-key.rst b/cookbook/security/guard-api-key.rst deleted file mode 100644 index abf4967769d..00000000000 --- a/cookbook/security/guard-api-key.rst +++ /dev/null @@ -1,489 +0,0 @@ -.. index:: - single: Security; Custom Request Authenticator - -How to Authenticate Users with an API Key using Guard -===================================================== - -Nowadays, it's quite usual to authenticate the user via an API key (when developing -a web service for instance). The API key is provided for every request and is -passed as a query string parameter or via an HTTP header. - -Setting up the authenticator is just 3 steps: - -* :ref:`cookbook-guard-api-authenticator` -* :ref:`cookbook-guard-api-configuration` -* :ref:`cookbook-guard-api-user-provider` - -.. _cookbook-guard-api-authenticator: - -A) Create the Guard Authenticator ---------------------------------- - -Suppose you want to read an ``X-API-TOKEN`` header on each request and use -that to authenticate the user. To do this, create a class that extends -:class:`Symfony\\Component\\Security\\Guard\\AbstractGuardAuthenticator` -(or which implements :class:`Symfony\\Component\\Security\\Guard\\GuardAuthenticatorInterface`):: - - // src/AppBundle/Security/TokenAuthenticator.php - namespace AppBundle\Security; - - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; - use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; - use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; - use Symfony\Component\Security\Core\Exception\AuthenticationException; - use Symfony\Component\Security\Core\User\UserProviderInterface; - - class TokenAuthenticator extends AbstractGuardAuthenticator - { - public function getCredentialsFromRequest(Request $request) - { - $token = $request->headers->get('X-AUTH-TOKEN'); - - // no token? Don't do anything :D - if (!$token) { - return; - } - - return array( - 'token' => $token, - ); - } - - public function authenticate($credentials, UserProviderInterface $userProvider) - { - $token = $credentials['token']; - - // call a method on your UserProvider - see below for details - $user = $userProvider->loadUserByToken($token); - - if (!$user) { - throw new AuthenticationCredentialsNotFoundException(); - } - - return $user; - } - - public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) - { - // on success, just let the request keep going! - return null; - } - - public function onAuthenticationFailure(Request $request, AuthenticationException $exception) - { - $data = array( - // you might translate this message - 'message' => $exception->getMessageKey() - ); - - return new Response(json_encode($data), 403); - } - - /** - * Called when authentication is needed, but it's not sent - */ - public function start(Request $request, AuthenticationException $authException = null) - { - $data = array( - // you might translate this message - 'message' => 'Authentication Required' - ); - - return new Response(json_encode($data), 401); - } - - public function supportsRememberMe() - { - return false; - } - } - -Nice work! You're not done yet, but let's look at each piece: - -**getCredentialsFromRequest()** - The guard system calls this method on *every* request and your job is - to read the token from the request and return it (it'll be passed to - ``authenticate()``). If you return ``null``, the rest of the authentication - process is skipped. - -**authenticate** - If you returned something from ``getCredentialsFromRequest()``, that - value is passed here as ``$credentials``. This is the *core* of the authentication - process. Your job is to use ``$credentials`` to return a ``UserInterface`` - object *or* throw an :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException` - (e.g. if the token does not exist). *How* you do this is up to you and - you'll learn more in :ref:`user provider ` - section. - -**onAuthenticationSuccess** - This is called after successful authentication and your job is to either - return a :class:`Symfony\\Component\\HttpFoundation\\Response` object - that will be sent to the client or ``null`` to continue the request - (e.g. allow the route/controller to be called like normal). Since this - is an API where each request authenticates itself, you want to return - ``nul``. - -**onAuthenticationFailure** - This is called if authentication fails (i.e. if you throw an - :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`) - inside ``authenticate()`` or ``getCredentialsFromRequest()``. Your job - is to return the :class:`Symfony\\Component\\HttpFoundation\\Response` - object that should be sent to the client. - -**start** - This is called if the client accesses a URI/resource that requires authentication, - but no authentication details were sent (i.e. you returned ``null`` from - ``getCredentialsFromRequest()``). Your job is to return a - :class:`Symfony\\Component\\HttpFoundation\\Response` object that helps - the user authenticate (e.g. a 401 response that says "token is missing!"). - -**supportsRememberMe** - Since this is a stateless API, you do not want to support "remember me" - functionality. - -.. _cookbook-guard-api-configuration: - -B) Configure the Service and security.yml ------------------------------------------ - -To use your configurator, you'll need to register it as a service: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - app.token_authenticator: - class: AppBundle\Security\TokenAuthenticator - arguments: [] - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/services.php - - // ... - $container - ->register('app.token_authenticator', 'AppBundle\Security\TokenAuthenticator'); - -Now you can configure your firewall to use the ``guard`` authentication system -and your new ``app.token_authenticator`` authenticator: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - - firewalls: - secured_area: - pattern: ^/ - # set to false if you *do* want to store users in the session - stateless: true - anonymous: true - guard: - authenticators: - - app.token_authenticator - - .. code-block:: xml - - - - - - - - - - apikey_authenticator - - - - - - .. code-block:: php - - // app/config/security.php - - // .. - - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'secured_area' => array( - 'pattern' => '^/', - 'stateless' => true, - 'anonymous' => true, - 'guard' => array( - 'authenticators' => array( - 'app.token_authenticator' - ), - ), - ), - ), - )); - -Perfect! Now that the security system knows about your authenticator, the -last step is to configure your "user provider" and make it work nicely with -the authenticator. - -C) Adding the User Provider ---------------------------- - -Even though you're authenticating with an API token, the end result is that -a "User" object is set on the security system. Returning this user is the goal -of your configurator's ``authenticate()`` method. - -To help out, you'll need to configure a :ref:`user provider `. -A few core providers exist (including one that :ref:`loads users from Doctrine `), -but creating one that does exactly what you want is easy. - -Suppose you're using Doctrine and have a ``User`` entity that has a ``token`` -property that can be used to authenticate with your API (the ``User`` entity -class isn't shown here). To load users from that entity, create a class that -implements :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`:: - - namespace AppBundle\Security; - - use AppBundle\Repository\UserRepository; - use Doctrine\ORM\EntityManager; - use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; - use Symfony\Component\Security\Core\User\UserInterface; - use Symfony\Component\Security\Core\User\UserProviderInterface; - - class UserProvider implements UserProviderInterface - { - private $em; - - public function __construct(EntityManager $em) - { - $this->em = $em; - } - - // UserProviderInterface - public function loadUserByUsername($username) - { - $user = $this->getUserRepository()->findOneBy(array('username' => $username)); - - if (null === $user) { - throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); - } - - return $user; - } - - // UserProviderInterface - public function refreshUser(UserInterface $user) - { - $user = $this->getUserRepository()->find($user->getId()); - if (!$user) { - throw new UsernameNotFoundException(sprintf('User with id "%s" not found!', $user->getId())); - } - - return $user; - } - - // UserProviderInterface - public function supportsClass($class) - { - return $class === get_class($this) || is_subclass_of($class, get_class($this)); - } - - // our own custom method - public function loadUserByToken($token) - { - return $this->getUserRepository()->findOneBy(array( - 'token' => $token - )); - } - - /** - * @return UserRepository - */ - private function getUserRepository() - { - return $this->em->getRepository('AppBundle:User'); - } - } - -Most of these methods are part of ``UserInterface`` and are used internally. -But ``loadUserByToken()`` is a custom method that you'll use in a moment. - -Register your brand-new "user provider" as a service: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/services.yml - services: - app.user_provider: - class: AppBundle\Security\UserProvider - arguments: ['@doctrine.orm.entity_manager'] - - - .. code-block:: xml - - - - - - - - - doctrine.orm.entity_manager - - - - - .. code-block:: php - - // app/config/services.php - - // ... - $container - ->register('app.user_provider', 'AppBundle\Security\UserProvider') - ->setArguments(array( - new Reference('doctrine.orm.entity_manager') - )); - -And finally plug this into your security system and tell your firewall that -this is your provider: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - - providers: - # this key could be anything, but it's referenced below - main_provider: - id: app.user_provider - - firewalls: - secured_area: - # all the existing stuff ... - provider: main_provider - - .. code-block:: xml - - - - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - - // .. - - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'secured_area' => array( - // ... - 'provider' => 'main_provider' - ), - ), - 'providers' => array( - 'main_provider' => array( - 'id' => 'app.user_provider', - ), - ), - )); - -Great work! Because of this, when ``authenticate()`` is called on your ``TokenAuthenticator``, -the ``$userProvider`` argument will be *your* ``UserProvider`` class. This -means you can add whatever methods to ``UserProvider`` that you want, and -then use them inside ``authenticate()``. - -As a reminder, the ``TokenAuthenticator::authenticate()`` looks like this:: - - // src/AppBundle/Security/TokenAuthenticator.php - // ... - - public function authenticate($credentials, UserProviderInterface $userProvider) - { - $token = $credentials['token']; - - // call a method on your UserProvider - see below for details - $user = $userProvider->loadUserByToken($token); - - if (!$user) { - throw new AuthenticationCredentialsNotFoundException(); - } - - return $user; - } - -To query for a ``User`` object whose ``token`` property matches the ``$token``, -you can use the ``UserProvider::loadUserByToken()`` that was added a moment -ago. It makes that query and reutrns the ``User`` object. - -This is just *one* example: you could add whatever methods to ``UserProvider`` -that you want or use whatever logic you want to return the ``User`` object. - -Doing more with Guard Auth --------------------------- - -Now that you know how to authenticate with a token, see what else you can -do: - -Doing more with Guard Auth --------------------------- - -Now that you know how to authenticate with a token, see what else you can -do: - -* :doc:`Creating a Login Form ` -* :doc:`Using Multiple Authenticators (Login form *and* API Token) ` - diff --git a/cookbook/security/guard-authentication.rst b/cookbook/security/guard-authentication.rst index c27886c264e..30d158344e2 100644 --- a/cookbook/security/guard-authentication.rst +++ b/cookbook/security/guard-authentication.rst @@ -1,11 +1,576 @@ .. index:: single: Security; Custom Authentication -How to Authenticate in any way using Guard -========================================== +How to Create a Custom Authentication System with Guard +======================================================= -Whether you're creating a traditional login form or authenticating via an -API token, using the guard authentication system will make this painless. +Whether you need to build a traditional login form, an API token authentication system +or you need to integrate with some proprietary single-sign-on system, the Guard +component can make it easy... and fun! + +In this example, you'll build an API token authentication system... and learn how +to work with Guard. + +Create a User and a User Provider +--------------------------------- + +No matter how you authenticate, you need to create a User class that implements ``UserInterface`` +and configure a :doc:`user provider `. In this +example, users are stored in the database via Doctrine, and each user has an ``apiKey`` +property they can use to access their account via the API:: + + // src/AppBundle/Entity/User.php + namespace AppBundle\Entity; + + use Symfony\Component\Security\Core\User\UserInterface; + use Doctrine\ORM\Mapping as ORM; + + /** + * @ORM\Entity + * @ORM\Table(name="user") + */ + class User implements UserInterface + { + /** + * @ORM\Id + * @ORM\GeneratedValue(strategy="AUTO") + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="string", unique=true) + */ + private $username; + + /** + * @ORM\Colum(type"string", unique=true) + */ + private $apiKey; + + public function getUsername() + { + return $this->username; + } + + public function getRoles() + { + return ['ROLE_USER']; + } + + public function getPassword() + { + } + public function getSalt() + { + } + public function eraseCredentials() + { + } + + // more getters/setters + } + +.. tip:: + + This User doesn't have a password, but you can add a ``password`` property if + you also want to allow this user to login with a password (e.g. via a login form). + +Your ``User`` class doesn't need to be store in Doctrine: do whatever you need. +Next, make sure you've configured a "user provider" for the user: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + # ... + + providers: + your_db_provider: + entity: + class: AppBundle:User + + # ... + + .. code-block:: xml + + + + + + + + + + + + + + + + + .. code-block:: php + + // app/config/security.php + $container->loadFromExtension('security', array( + // ... + + 'providers' => array( + 'your_db_provider' => array( + 'entity' => array( + 'class' => 'AppBundle:User', + ), + ), + ), + + // ... + )); + +That's it! Need more information about this step, see: + +* :doc:`/cookbook/security/entity_provider` +* :doc:`/cookbook/security/custom_provider` + +Step 1) Create the Authenticator Class +-------------------------------------- + +To create a custom authentication system, just create a class an make it implement +:class:`Symfony\\Component\\Security\\Guard\\GuardAuthenticatorInterface`. Or, extend +the simpler :class:`Symfony\\Component\\Security\\Guard\\AbstractGuardAuthenticator`. +This requires you to implement six methods:: + + // src/AppBundle/Security/TokenAuthenticator.php + namespace AppBundle\Security; + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\Security\Core\User\UserInterface; + use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; + use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Core\Exception\AuthenticationException; + use Symfony\Component\Security\Core\User\UserProviderInterface; + use Doctrine\ORM\EntityManager; + + class TokenAuthenticator extends AbstractGuardAuthenticator + { + private $em; + + public function __construct(EntityManager $em) + { + $this->em = $em; + } + + /** + * Called on every request. Return whatever credentials you want, + * or null to stop authentication. + */ + public function getCredentials(Request $request) + { + if (!$token = $request->headers->get('X-AUTH-TOKEN')) { + // no token? Return null and no other methods will be called + return; + } + + // What we return here will be passed to getUser() as $credentials + return array( + 'token' => $token, + ); + } + + public function getUser($credentials, UserProviderInterface $userProvider) + { + $apiToken = $credentials['token']; + + // if null, authentication will fail + // if a User object, checkCredentials() is called + return $this->em->getRepository('AppBundle:User') + ->findOneBy(array('apiToken' => $apiToken)); + } + + public function checkCredentials($credentials, UserInterface $user) + { + // check credentials - e.g. make sure the password is valid + // no credential check is needed in this case + + // return true to cause authentication success + return true; + } + + public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) + { + // on success, let the request continue + return null; + } + + public function onAuthenticationFailure(Request $request, AuthenticationException $exception) + { + $data = array( + 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()) + + // or to translate this message + // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()) + ); + + return new Response(json_encode($data), 403); + } + + /** + * Called when authentication is needed, but it's not sent + */ + public function start(Request $request, AuthenticationException $authException = null) + { + $data = array( + // you might translate this message + 'message' => 'Authentication Required' + ); + + return new Response(json_encode($data), 401); + } + + public function supportsRememberMe() + { + return false; + } + } + +Nice work! Each method is explained below: :ref:`The Guard Authenticator Methods`. + +Step 2) Configure the Authenticator +----------------------------------- + +To finish this, register the class as a service: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + app.token_authenticator: + class: AppBundle\Security\TokenAuthenticator + arguments: ['@doctrine.orm.entity_manager'] + + .. code-block:: xml + + + + + + + + + .. code-block:: php + + // app/config/services.php + use Symfony\Component\DependencyInjection\Definition; + use Symfony\Component\DependencyInjection\Reference; + + $container->setDefinition('app.token_authenticator', new Definition( + 'AppBundle\Security\TokenAuthenticator', + array(new Reference('doctrine.orm.entity_manager')) + )); + +Finally, configure your ``firewalls`` key in ``security.yml`` to use this authenticator: + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + # ... + + firewalls: + # ... + + main: + anonymous: ~ + logout: ~ + + guard: + authenticators: + - app.token_authenticator + + # if you want, disable storing the user in the session + # stateless: true + + # maybe other things, like form_login, remember_me, etc + # ... + + .. code-block:: xml + + + + + + + + + + + + app.token_authenticator + + + + + + + + .. code-block:: php + + // app/config/security.php + + // .. + + $container->loadFromExtension('security', array( + 'firewalls' => array( + 'main' => array( + 'pattern' => '^/', + 'anonymous' => true, + 'logout' => true, + 'guard' => array( + 'authenticators' => array( + 'app.token_authenticator' + ), + ), + // ... + ), + ), + )); + +You did it! You now have a fully-working API token authentication system. If you're +homepage required ``ROLE_USER``, then you could test it under different conditions: + +.. code-block:: bash + + # test with no token + curl http://localhost:8000/ + # {"message":"Authentication Required"} + + # test with a bad token + curl -H "X-AUTH-TOKEN: FAKE" http://localhost:8000/ + # {"message":"Username could not be found."} + + # test with a working token + curl -H "X-AUTH-TOKEN: REAL" http://localhost:8000/ + # the homepage controller is executed: the page loads normally + +Now, learn more about what each method does. + +.. _guard-auth-methods: + +The Guard Authenticator Methods +------------------------------- + +Each authenticator needs the following methods: + +**getCredentials(Request $request)** + This will be called on *every* request and your job is to read the token (or + whatever your "authentication" information is) from the request and return it. + If you return ``null``, the rest of the authentication process is skipped. Otherwise, + ``getUser()`` will be called and the return value is passed as the first argument. + +**getUser($credentials, UserProviderInterface $userProvider)** + If ``getCredentials()`` returns a non-null value, then this method is called + and its return value is passed here as the ``$credentials`` argument. Your job + is to return an object that implements ``UserInterface``. If you do, then + ``checkCredentials()`` will be called. If you return ``null`` (or throw an + :ref:`AuthenticationException `) + authentication will fail. + +**checkCredentials($credentials, UserInterface $user)** + If ``getUser()`` returns a User object, this method is called. Your job is to + verify if the credentials are correct. For a login for, this is where you would + check that the password is correct for the user. To pass authentication, return + ``true``. If you return *anything* else + (or throw an :ref:`AuthenticationException `), + authentication will fail. + +**onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)** + This is called after successful authentication and your job is to either + return a :class:`Symfony\\Component\\HttpFoundation\\Response` object + that will be sent to the client or ``null`` to continue the request + (e.g. allow the route/controller to be called like normal). Since this + is an API where each request authenticates itself, you want to return + ``nul``. + +**onAuthenticationFailure(Request $request, AuthenticationException $exception)** + This is called if authentication fails. Your job + is to return the :class:`Symfony\\Component\\HttpFoundation\\Response` + object that should be sent to the client. The ``$exception`` will tell you + *what* went wrong during authentication. + +**start** + This is called if the client accesses a URI/resource that requires authentication, + but no authentication details were sent (i.e. you returned ``null`` from + ``getCredentialsFromRequest()``). Your job is to return a + :class:`Symfony\\Component\\HttpFoundation\\Response` object that helps + the user authenticate (e.g. a 401 response that says "token is missing!"). + +**supportsRememberMe** + Since this is a stateless API, you do not want to support "remember me" + functionality. + +.. _guard-customize-error: + +Customizing Error Messages +-------------------------- + +When ``onAuthenticationFailure()`` is called, it is passed an ``AuthenticationException`` +that describes *how* authentication failed via its ``$e->getMessageKey()`` (and +``$e->getMessageData()``) method. The message will be different based on *where* +authentication fails (i.e. ``getUser()`` versus ``checkCredentials()``). + +But, you can easily return a custom message by throwing a +:class:`Symfony\\Component\\Security\\Core\\Exception\\CustomUserMessageAuthenticationException`. +You can throw this from ``getCredentials()``, ``getUser()`` or ``checkCredentials()`` +to cause a failure:: + + // src/AppBundle/Security/TokenAuthenticator.php + // ... + + use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; + + class TokenAuthenticator extends AbstractGuardAuthenticator + { + // ... + + public function getCredentials(Request $request) + { + // ... + + if ($token == 'MickyMouse') { + throw new CustomUserMessageAuthenticationException( + 'MickyMouse is not a real API key: he\'s a cartoon character' + ); + } + + // ... + } + + // ... + } + +In this case, since "MickyMouse" is a ridiculous API key, you could include an easter +egg to return a custom message if someone tries this: + +.. code-block:: bash + + curl -H "X-AUTH-TOKEN: MickyMouse" http://localhost:8000/ + # {"message":"MickyMouse is not a real API key: he's a cartoon character"} + +Frequently Asked Questions +-------------------------- + +**Can I have Multiple Authenticators?** + Yes! But when you do, you'll need choose just *one* authenticator to be your + "entry_point". This means you'll need to choose *which* authenticator's ``start()`` + method should be called when an anonymous user tries to access a protected resource. + For example, suppose you have an ``app.form_login_authenticator`` that handles + a traditional form login. When a user access a protected page anonymously, you + want to use the ``start()`` method from the form authenticator and redirect them + to the login page (instead of returning a JSON response): + + .. configuration-block:: + + .. code-block:: yaml + + # app/config/security.yml + security: + # ... + + firewalls: + # ... + + main: + anonymous: ~ + logout: ~ + + guard: + authenticators: + - app.token_authenticator + + # if you want, disable storing the user in the session + # stateless: true + + # maybe other things, like form_login, remember_me, etc + # ... + + .. code-block:: xml + + + + + + + + + + + + app.token_authenticator + + + + + + + + .. code-block:: php + + // app/config/security.php + + // .. + + $container->loadFromExtension('security', array( + 'firewalls' => array( + 'main' => array( + 'pattern' => '^/', + 'anonymous' => true, + 'logout' => true, + 'guard' => array( + 'authenticators' => array( + 'app.token_authenticator' + ), + ), + // ... + ), + ), + )); + +**Can I use this with ``form_login``?** + Yes! ``form_login`` is *one* way to authenticator a user, so you could use + it *and* then add one more more authenticators. Use a guard authenticator doesn't + collide with other ways to authenticate. + +**Can I use this with FOSUserBundle?** + Yes! Actually, FOSUserBundle doesn't handle security: it simply gives you a + ``User`` object and some routes and controllers to help with login, registration, + forgot password, etc. When you use FOSUserBundle, you typically use ``form_login`` + to actually authenticate the user. You can continue doing that (see previous + question) or use the ``User`` object from FOSUserBundle and create your own + authenticator(s) (just like in this article). * :doc:`Creating a Login Form ` * :doc:`Authenticating with an API Token ` From 62dcae3b2d17228a84e7378f30b898cd7057e3a0 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 22 Nov 2015 18:35:19 -0500 Subject: [PATCH 5/8] Using JsonResponse + cleanup --- cookbook/security/guard-authentication.rst | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cookbook/security/guard-authentication.rst b/cookbook/security/guard-authentication.rst index 30d158344e2..af0656e23f9 100644 --- a/cookbook/security/guard-authentication.rst +++ b/cookbook/security/guard-authentication.rst @@ -149,7 +149,7 @@ This requires you to implement six methods:: namespace AppBundle\Security; use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Guard\AbstractGuardAuthenticator; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -217,7 +217,7 @@ This requires you to implement six methods:: // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()) ); - return new Response(json_encode($data), 403); + return new JsonResponse($data, 403); } /** @@ -230,7 +230,7 @@ This requires you to implement six methods:: 'message' => 'Authentication Required' ); - return new Response(json_encode($data), 401); + return new JsonResponse($data, 401); } public function supportsRememberMe() @@ -571,7 +571,3 @@ Frequently Asked Questions to actually authenticate the user. You can continue doing that (see previous question) or use the ``User`` object from FOSUserBundle and create your own authenticator(s) (just like in this article). - -* :doc:`Creating a Login Form ` -* :doc:`Authenticating with an API Token ` -* :doc:`Using Multiple Authenticators (Login form *and* API Token) ` From 9782ff1bd657dfa0a812b99a2c96610d9bfb6024 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 22 Nov 2015 18:37:39 -0500 Subject: [PATCH 6/8] adding toc entries --- cookbook/map.rst.inc | 1 + cookbook/security/index.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/cookbook/map.rst.inc b/cookbook/map.rst.inc index 8049b6b320d..bcbe7e00c91 100644 --- a/cookbook/map.rst.inc +++ b/cookbook/map.rst.inc @@ -161,6 +161,7 @@ * :doc:`/cookbook/security/form_login_setup` * :doc:`/cookbook/security/entity_provider` + * :doc:`/cookbook/security/guard-authentication` * :doc:`/cookbook/security/remember_me` * :doc:`/cookbook/security/impersonating_user` * :doc:`/cookbook/security/form_login` diff --git a/cookbook/security/index.rst b/cookbook/security/index.rst index c9a478c927a..bdbeaab10d8 100644 --- a/cookbook/security/index.rst +++ b/cookbook/security/index.rst @@ -9,6 +9,7 @@ Authentication (Identifying/Logging in the User) form_login_setup entity_provider + guard-authentication remember_me impersonating_user form_login From 4752d4c41eca8bd5887b902df44dd17abfb9a745 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 22 Nov 2015 20:06:08 -0500 Subject: [PATCH 7/8] adding one clarifying message --- cookbook/security/guard-authentication.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cookbook/security/guard-authentication.rst b/cookbook/security/guard-authentication.rst index af0656e23f9..23f2bcceb60 100644 --- a/cookbook/security/guard-authentication.rst +++ b/cookbook/security/guard-authentication.rst @@ -140,6 +140,10 @@ That's it! Need more information about this step, see: Step 1) Create the Authenticator Class -------------------------------------- +Suppose you have an API where your clients will send an ``X-AUTH-TOKEN`` header +on each request with their API token. Your job is to read this and find the associated +user (if any). + To create a custom authentication system, just create a class an make it implement :class:`Symfony\\Component\\Security\\Guard\\GuardAuthenticatorInterface`. Or, extend the simpler :class:`Symfony\\Component\\Security\\Guard\\AbstractGuardAuthenticator`. From 51720c76fe819a2bedab4fef88874773bfae7aba Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Fri, 27 Nov 2015 14:16:59 -0500 Subject: [PATCH 8/8] Many fixes thanks to great review from ogizanagi, javiereguiluz and others --- cookbook/security/guard-authentication.rst | 40 ++++++++++++---------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/cookbook/security/guard-authentication.rst b/cookbook/security/guard-authentication.rst index 23f2bcceb60..5201fcf2bbb 100644 --- a/cookbook/security/guard-authentication.rst +++ b/cookbook/security/guard-authentication.rst @@ -8,7 +8,7 @@ Whether you need to build a traditional login form, an API token authentication or you need to integrate with some proprietary single-sign-on system, the Guard component can make it easy... and fun! -In this example, you'll build an API token authentication system... and learn how +In this example, you'll build an API token authentication system and learn how to work with Guard. Create a User and a User Provider @@ -17,7 +17,7 @@ Create a User and a User Provider No matter how you authenticate, you need to create a User class that implements ``UserInterface`` and configure a :doc:`user provider `. In this example, users are stored in the database via Doctrine, and each user has an ``apiKey`` -property they can use to access their account via the API:: +property they use to access their account via the API:: // src/AppBundle/Entity/User.php namespace AppBundle\Entity; @@ -76,7 +76,7 @@ property they can use to access their account via the API:: This User doesn't have a password, but you can add a ``password`` property if you also want to allow this user to login with a password (e.g. via a login form). -Your ``User`` class doesn't need to be store in Doctrine: do whatever you need. +Your ``User`` class doesn't need to be stored in Doctrine: do whatever you need. Next, make sure you've configured a "user provider" for the user: .. configuration-block:: @@ -124,7 +124,7 @@ Next, make sure you've configured a "user provider" for the user: 'providers' => array( 'your_db_provider' => array( 'entity' => array( - 'class' => 'AppBundle:User', + 'class' => 'AppBundle:User', ), ), ), @@ -144,7 +144,7 @@ Suppose you have an API where your clients will send an ``X-AUTH-TOKEN`` header on each request with their API token. Your job is to read this and find the associated user (if any). -To create a custom authentication system, just create a class an make it implement +To create a custom authentication system, just create a class and make it implement :class:`Symfony\\Component\\Security\\Guard\\GuardAuthenticatorInterface`. Or, extend the simpler :class:`Symfony\\Component\\Security\\Guard\\AbstractGuardAuthenticator`. This requires you to implement six methods:: @@ -181,7 +181,7 @@ This requires you to implement six methods:: return; } - // What we return here will be passed to getUser() as $credentials + // What you return here will be passed to getUser() as $credentials return array( 'token' => $token, ); @@ -356,7 +356,7 @@ Finally, configure your ``firewalls`` key in ``security.yml`` to use this authen ), )); -You did it! You now have a fully-working API token authentication system. If you're +You did it! You now have a fully-working API token authentication system. If your homepage required ``ROLE_USER``, then you could test it under different conditions: .. code-block:: bash @@ -398,7 +398,7 @@ Each authenticator needs the following methods: **checkCredentials($credentials, UserInterface $user)** If ``getUser()`` returns a User object, this method is called. Your job is to - verify if the credentials are correct. For a login for, this is where you would + verify if the credentials are correct. For a login form, this is where you would check that the password is correct for the user. To pass authentication, return ``true``. If you return *anything* else (or throw an :ref:`AuthenticationException `), @@ -410,7 +410,7 @@ Each authenticator needs the following methods: that will be sent to the client or ``null`` to continue the request (e.g. allow the route/controller to be called like normal). Since this is an API where each request authenticates itself, you want to return - ``nul``. + ``null``. **onAuthenticationFailure(Request $request, AuthenticationException $exception)** This is called if authentication fails. Your job @@ -421,13 +421,15 @@ Each authenticator needs the following methods: **start** This is called if the client accesses a URI/resource that requires authentication, but no authentication details were sent (i.e. you returned ``null`` from - ``getCredentialsFromRequest()``). Your job is to return a + ``getCredentials()``). Your job is to return a :class:`Symfony\\Component\\HttpFoundation\\Response` object that helps the user authenticate (e.g. a 401 response that says "token is missing!"). **supportsRememberMe** + If you want to support "remember me" functionality, return true from this method. + You will still need to active ``rememebe_me`` under your firewall for it to work. Since this is a stateless API, you do not want to support "remember me" - functionality. + functionality in this example. .. _guard-customize-error: @@ -457,9 +459,9 @@ to cause a failure:: { // ... - if ($token == 'MickyMouse') { + if ($token == 'ILuvAPIs') { throw new CustomUserMessageAuthenticationException( - 'MickyMouse is not a real API key: he\'s a cartoon character' + 'ILuvAPIs is not a real API key: it\'s just a silly phrase' ); } @@ -469,13 +471,13 @@ to cause a failure:: // ... } -In this case, since "MickyMouse" is a ridiculous API key, you could include an easter +In this case, since "ILuvAPIs" is a ridiculous API key, you could include an easter egg to return a custom message if someone tries this: .. code-block:: bash - curl -H "X-AUTH-TOKEN: MickyMouse" http://localhost:8000/ - # {"message":"MickyMouse is not a real API key: he's a cartoon character"} + curl -H "X-AUTH-TOKEN: ILuvAPIs" http://localhost:8000/ + # {"message":"ILuvAPIs is not a real API key: it's just a silly phrase"} Frequently Asked Questions -------------------------- @@ -485,7 +487,7 @@ Frequently Asked Questions "entry_point". This means you'll need to choose *which* authenticator's ``start()`` method should be called when an anonymous user tries to access a protected resource. For example, suppose you have an ``app.form_login_authenticator`` that handles - a traditional form login. When a user access a protected page anonymously, you + a traditional form login. When a user accesses a protected page anonymously, you want to use the ``start()`` method from the form authenticator and redirect them to the login page (instead of returning a JSON response): @@ -564,8 +566,8 @@ Frequently Asked Questions )); **Can I use this with ``form_login``?** - Yes! ``form_login`` is *one* way to authenticator a user, so you could use - it *and* then add one more more authenticators. Use a guard authenticator doesn't + Yes! ``form_login`` is *one* way to authenticate a user, so you could use + it *and* then add one or more authenticators. Using a guard authenticator doesn't collide with other ways to authenticate. **Can I use this with FOSUserBundle?**